home *** CD-ROM | disk | FTP | other *** search
/ Microsoft Programmer's Library 1.3 / Microsoft_Programmers_Library.7z / MPL / os2 / os2pm.txt < prev    next >
Encoding:
Text File  |  2013-11-08  |  1.4 MB  |  36,486 lines

Text Truncated. Only the first 1MB is shown below. Download the file for the complete contents.
  1.  Programming The OS/2 Presentation Manager
  2.  
  3.  The Microsoft(R) Guide to Writing Applications for the OS/2 Graphical
  4.  Windowing Eviornment
  5.  
  6.  PUBLISHED BY
  7.  
  8.  Microsoft Press
  9.  A Division of Microsoft Corporation
  10.  16011 NE 36th Way, Box 97017, Redmond, Washington 98073-9717
  11.  
  12.  Copyright(C) 1989 by Charles Petzold
  13.  All rights reserved. No part of the contents of this book may be reproduced
  14.  or transmitted in any form or by any means without the written permission of
  15.  the publisher.
  16.  
  17.  Library of Congress Cataloging in Publication Data
  18.  
  19.  Petzold, Charles, 1953-
  20.  Programming the OS/2 Presentation Manager.
  21.  Includes index.
  22.  1. OS/2 (Computer operating system)
  23.  2. Presentation Manager (Computer program)
  24.  3. Computer graphics.
  25.  4. C (Computer program language)
  26.  I. Title.
  27.  QA76.76.063P53    1989    005.4'469    88-27368
  28.  
  29.  ISBN 1-55615-170-5
  30.  
  31.  Printed and bound in the United States of America.
  32.  1 2 3 4 5 6 7 8 9   MLML 5 4 3 2 1 0 9
  33.  
  34.  Distributed to the book trade in the United States by Harper & Row.
  35.  
  36.  Distributed to the book trade in Canada by General Publishing Company, Ltd.
  37.  
  38.  Distributed to the book trade outside the United States and Canada by
  39.  Penguin Books Ltd.
  40.  
  41.  Penguin Books Ltd., Harmondsworth, Middlesex, England
  42.  Penguin Books Australia Ltd., Ringwood, Victoria, Australia
  43.  Penguin Books N.Z. Ltd., 182-190 Wairau Road, Auckland 10, New Zealand
  44.  
  45.  British Cataloging in Publication Data available
  46.  
  47.  Microsoft(R) and MS-DOS(R) are registered trademarks of Microsoft
  48.  Corporation. IBM(R) and PC/AT(R) are registered trademarks of International
  49.  Business Machines Corporation.
  50.  
  51.  Project Editor: Megan E. Sheppard
  52.  Technical Editor: Mary B. Ottaway
  53.  
  54.  
  55.  Table of Contents
  56.  
  57.  Preface
  58.  
  59.  SECTION ONE       BASIC CONCEPTS
  60.  CHAPTER ONE       OS/2 and the Presentation Manager
  61.  CHAPTER TWO       Welcome to Presentation Manager Programming
  62.  CHAPTER THREE     More Fun with Windows
  63.  
  64.  SECTION TWO       PAINTING THE CLIENT WINDOW
  65.  CHAPTER FOUR      An Exercise in Text Output
  66.  CHAPTER FIVE      The Five GPI Primitives
  67.  CHAPTER SIX       Bitmaps and Bitblts
  68.  CHAPTER SEVEN     Advanced VIO: The Easy Way Out
  69.  
  70.  SECTION THREE     GETTING INPUT
  71.  CHAPTER EIGHT     Tapping Into the Keyboard
  72.  CHAPTER NINE      Taming the Mouse
  73.  CHAPTER TEN       Setting the Timer
  74.  CHAPTER ELEVEN    Control Windows: Putting the Children to Work
  75.  
  76.  SECTION FOUR      USING RESOURCES
  77.  CHAPTER TWELVE    Bitmaps, Icons, Pointers, and Strings
  78.  CHAPTER THIRTEEN  Menus and Keyboard Accelerators
  79.  CHAPTER FOURTEEN  Dialog Boxes
  80.  
  81.  SECTION FIVE      MISCELLANEOUS TOPICS
  82.  CHAPTER FIFTEEN   Cut, Copy, and Paste: The Clipboard
  83.  CHAPTER SIXTEEN   Dynamic Link Libraries
  84.  CHAPTER SEVENTEEN Multithread Programming Techniques
  85.  
  86.  Index
  87.  
  88.  
  89.  Preface
  90.  
  91.  This book covers the basics of programming in C for the Presentation
  92.  Manager, the graphical windowing environment included in OS/2 version 1.1.
  93.  The Presentation Manager combines a powerful protected mode multitasking
  94.  operating system (OS/2) with the application architecture and user interface
  95.  of Microsoft Windows version 2 and a sophisticated graphics system from IBM.
  96.  
  97.  The OS/2 Presentation Manager runs on IBM (and compatible) PCs and PS/2s
  98.  that are based on the Intel 80286 and 80386 microprocessors. Although the
  99.  Presentation Manager was designed for OS/2, IBM intends to port it to its
  100.  minicomputers and mainframes, and Microsoft has announced plans to help port
  101.  the Presentation Manager to run under UNIX.
  102.  
  103.  I'm writing this preface just 37 days after OS/2 1.1 was officially
  104.  released. Yet it already seems likely that the Presentation Manager will be
  105.  the dominant application environment for small computers in the 1990s.
  106.  Programmers, of course, will be responsible for writing these applications.
  107.  The purpose of this book is to help aspiring Presentation Manager
  108.  programmers get started in that job.
  109.  
  110.  
  111.  My Assumptions About You
  112.  
  113.  I assume that you know how to program in C. If you don't, programming for
  114.  the OS/2 Presentation Manager is probably not a good place to begin. I
  115.  suggest you learn C programming for a more conventional environment such as
  116.  the OS/2 kernel or DOS. You should have a comfortable familiarity with C
  117.  pointers and structures, as well as an understanding of the concepts of
  118.  "near" and "far" as they apply to the segmented-memory architecture of the
  119.  Intel microprocessors.
  120.  
  121.  I'm also assuming that you have some experience using the Presentation
  122.  Manager. If not, take some time and play around with it.
  123.  
  124.  I do not assume that you have any experience with programming for other
  125.  graphical windowing environments such as the Apple Macintosh or Microsoft
  126.  Windows. Experience with these environments might help with some of the
  127.  initial conceptual hurdles, but it's not necessary.
  128.  
  129.  
  130.  What You'll Need
  131.  
  132.  To compile and run the programs in this book, you need the following
  133.  software installed on your hard disk:
  134.  
  135.    ■  IBM OS/2 1.1 (if you have an IBM PC or PS/2) or a version of MS OS/2
  136.       1.1 available from the manufacturer of your computer
  137.  
  138.    ■  The Microsoft OS/2 Software Development Kit, or The Microsoft OS/2
  139.       Programmer's Toolkit 1.1, or The Microsoft OS/2 Presentation Manager
  140.       Softset
  141.  
  142.    ■  The Microsoft C Compiler, version 5.1 (or later)
  143.  
  144.  In addition, two of the programs shown in this book require the Microsoft
  145.  Macro Assembler, version 5.1, but you can skip these programs if you want.
  146.  
  147.  Both the Microsoft OS/2 Software Development Kit and the Microsoft OS/2
  148.  Programmer's Toolkit 1.1 have the header files you'll need for Presentation
  149.  Manager programming, as well as the OS2.LIB import library, the resource
  150.  compiler, development utilities such as ICONEDIT and DLGBOX, and technical
  151.  documentation. Although Programming the OS/2 Presentation Manager shows you
  152.  how to use many of the Presentation Manager function calls, it is not a
  153.  replacement for the official technical documentation. (Note that the
  154.  Microsoft OS/2 Presentation Manager Softset does not contain technical
  155.  documentation. You might want to take this into consideration when deciding
  156.  which package to purchase.)
  157.  
  158.  You should be able to write and compile programs using the IBM OS/2
  159.  Programmer's Toolkit 1.1, the IBM C/2 Compiler 1.1, and the IBM OS/2
  160.  Technical Reference 1.1, but having not seen these packages, I can't verify
  161.  this. Eventually, C compilers from other manufacturers will be suitable for
  162.  compiling Presentation Manager programs. Check with the compiler
  163.  manufacturer.
  164.  
  165.  The hardware you'll need to run these programs is the same hardware you need
  166.  to run OS/2 1.1. In addition, you should have a mouse. Although the
  167.  Presentation Manager does not require a mouse, some of the programs in this
  168.  book do.
  169.  
  170.  
  171.  Installing the C Compiler
  172.  
  173.  The sample programs in this book generally use small model (that is, the
  174.  .EXE files are compiled and linked to contain one code segment and one data
  175.  segment). The "make files" for these sample programs assume that the OS/2 C
  176.  run-time library for small model is named SLIBCE.LIB.
  177.  
  178.  If You've Already Installed the Compiler
  179.  
  180.  If you have already installed Microsoft C 5.1 for both OS/2 and DOS, the
  181.  installed run-time libraries might be named SLIBCEP.LIB (for OS/2 protected
  182.  mode) and SLIBCER.LIB (for real mode DOS). This is the default when you
  183.  install the compiler for both OS/2 and DOS. In this case, rename SLIBCEP.LIB
  184.  to SLIBCE.LIB.
  185.  
  186.  It's also conceivable that when you installed Microsoft C 5.1 for OS/2 and
  187.  DOS, you specified that you wanted to use the default library names for DOS.
  188.  In this case, the installed run-time libraries are named SLIBCEP.LIB (for
  189.  OS/2) and SLIBCE.LIB (for DOS). Rename SLIBCE.LIB to SLIBCER.LIB, and rename
  190.  SLIBCEP.LIB to SLIBCE.LIB.
  191.  
  192.  If you have already installed Microsoft C 5.1 and you know that your
  193.  SLIBCE.LIB file is the OS/2 run-time library (rather than the DOS run-time
  194.  library), you're in good shape.
  195.  
  196.  If You're Installing for the First Time
  197.  
  198.  If you are installing Microsoft C 5.1 for the first time, run the SETUP
  199.  program included with the compiler. The second screen you see will ask you
  200.  to make a number of decisions. You can choose the minimum configuration of
  201.  Microsoft C 5.1 required to compile the programs in this book by accepting
  202.  the defaults of five of the lines on this screen. You can handle the others
  203.  as you wish. The important defaults are
  204.  
  205.    ■  Build combined libraries.
  206.    ■  OS/2 Protect Mode libraries only.
  207.    ■  Emulator math library.
  208.    ■  Small memory model.
  209.    ■  Use default library names for the OS/2 libraries.
  210.  
  211.  This configuration builds a small-model C run-time library for OS/2 named
  212.  SLIBCE.LIB. Another question you're asked during the setup procedure is
  213.  whether or not to delete the library components. You can answer "Yes."
  214.  
  215.  If you prefer to install Microsoft C 5.1 for both OS/2 and DOS, you can do
  216.  so. You should specify that you want to use the default library names for
  217.  the OS/2 libraries but do not want to use the default library names for the
  218.  DOS libraries. In this case, the OS/2 run-time library is named SLIBCE.LIB,
  219.  and the DOS run-time library is named SLIBCER.LIB.
  220.  
  221.  Some programs and dynamic link libraries in the last two chapters of this
  222.  book do not use small model. These programs use special large-model
  223.  libraries that are designed for multithread programs and dynamic link
  224.  libraries. These libraries are always installed when you install Microsoft C
  225.  5.1 for OS/2, so you needn't do anything special to get them.
  226.  
  227.  
  228.  Other Books of Interest
  229.  
  230.  The OS/2 Presentation Manager is a very large system that includes about 500
  231.  function calls in addition to the 240 functions in OS/2 1.0. I cannot
  232.  pretend to cover them all in this book. In particular, my coverage of the
  233.  Graphics Programming Interface (GPI) component of the Presentation Manager
  234.  is restricted to the basics. A second book has been planned, and it will be
  235.  dedicated to an in-depth discussion of GPI.
  236.  
  237.  Before OS/2 1.1 there was OS/2 1.0, which is sometimes called the OS/2
  238.  kernel. Some of the programs in this book use OS/2 kernel functions.
  239.  Although I discuss these functions when necessary, you can find much more
  240.  information about the OS/2 kernel in Ray Duncan's Advanced OS/2 Programming
  241.  (Microsoft Press, 1989) and Augie Hansen's OS/2 Programming with Microsoft C
  242.  (Microsoft Press, 1989).
  243.  
  244.  
  245.  Inter-Programmer Communication
  246.  
  247.  If you'd like to get in touch with me, I can be reached through CompuServe
  248.  (72241,56) or MCI Mail (CPETZOLD or 143-6815).
  249.  
  250.  I can also frequently be found in the Programming forum of PC MagNet, PC
  251.  Magazine's information service available on CompuServe. To use this service,
  252.  just type go pcmagnet at a CompuServe prompt and follow the menus to the
  253.  Programming forum.
  254.  
  255.  
  256.  And Many Thanks
  257.  
  258.  Programming the OS/2 Presentation Manager was begun in August 1987 (at a
  259.  time when the Presentation Manager was barely functional) and was finished
  260.  in early December 1988, about five weeks after the product had been
  261.  officially released.
  262.  
  263.  This book would have been impossible to produce without the help of some
  264.  very fine people. I want to thank everyone at Microsoft Press who worked on
  265.  the book during these 16 months, all of whom bore with me as the chapters
  266.  and sample programs went through several series of revisions.
  267.  
  268.  A number of people at Microsoft also helped in various ways, by answering
  269.  questions, tracking down problems, offering suggestions and encouragement,
  270.  pointing out the existence of a very useful function call that I had
  271.  overlooked, or telling me I was doing something completely wrong. In
  272.  alphabetic order, they are Steve Ballmer, Larry Barello, Mark Cliggett, Lori
  273.  Hoerth, Doug Hogarth, Michael Hyman, Lionel Job, Neil Konzen, Jonathan
  274.  Lazarus, Mike Leu, Mark Mackaman, Ron Murray, Tony Rizzo, Manny Vellon, and
  275.  Ralph Walden. Thank you all very much.
  276.  
  277.  I'd also like to congratulate everyone else at IBM and Microsoft involved in
  278.  the design, development, and programming of the OS/2 Presentation Manager.
  279.  You did a great job, and you did it by October 1988.
  280.  
  281.  And many thanks to Jan and the Friday evenings that were my only refuge from
  282.  the world of windows, messages, and presentation spaces.
  283.  
  284.  Charles Petzold
  285.  
  286.  December 7, 1988
  287.  
  288.  
  289.  SECTION ONE       BASIC CONCEPTS
  290.  
  291.  Chapter 1  OS/2 and the Presentation Manager
  292.  ───────────────────────────────────────────────────────────────────────────
  293.  
  294.  
  295.  Programs designed to run under the OS/2 Presentation Manager share the video
  296.  display with other programs in a graphical windowing environment.
  297.  Presentation Manager programs are characterized by a consistent user
  298.  interface involving menus, dialog boxes, scroll bars, and other visual
  299.  devices that are accessible through either the keyboard or a pointing device
  300.  such as a mouse. Users generally find such an interface to be easily learned
  301.  and even mastered. Figure 1-1 on the following page shows several programs
  302.  from this book running under the Presentation Manager.
  303.  
  304.  The Presentation Manager user environment is reminiscent of systems
  305.  developed at the Xerox Palo Alto Research Center (PARC) in the 1970s and
  306.  early 1980s. In recent years, windowing environments have been popularized
  307.  by the Apple Macintosh and, under MS-DOS, by Microsoft Windows. The user
  308.  interface of the OS/2 Presentation Manager is the same interface used in
  309.  Windows 2.0, Windows/286, and Windows/386.
  310.  
  311.  For the program developer, the Presentation Manager has an extensive
  312.  application program interface (API) that includes many high-level functions
  313.  for creating windows and implementing the user interface. This API is
  314.  largely derived from Microsoft Windows; although the two systems aren't
  315.  exactly the same, they have many structural and conceptual similarities. The
  316.  OS/2 Presentation Manager also includes the Graphics Programming Interface
  317.  (GPI), a sophisticated graphics system adapted from IBM's Graphics Data
  318.  Display Manager (GDDM) and the 3270 Graphics Control Program (GCP), with
  319.  some elements inherited from the Windows Graphics Device Interface (GDI).
  320.  Because the Presentation Manager runs under OS/2, programs designed for the
  321.  environment can also take advantage of preemptive, priority-based
  322.  multitasking, 16 megabytes of physical memory, virtual memory management,
  323.  and interprocess communication.
  324.  
  325.  This book shows you how to write programs for the OS/2 Presentation Manager.
  326.  If you have some experience programming for Microsoft Windows or the Apple
  327.  Macintosh, you're in good shape. But if your programming experience is
  328.  limited to more conventional operating systems (such as MS-DOS, the OS/2
  329.  kernel, or UNIX), you need to put aside your preconceptions of how programs
  330.  work and brace yourself for some strange ideas. We're off on a voyage to a
  331.  new world.
  332.  
  333.  
  334.  The Big Picture
  335.  
  336.  Developed by Microsoft and IBM as a successor to MS-DOS, OS/2 is an
  337.  operating system for small computers based on the Intel 80286 and 80386
  338.  microprocessors. OS/2 uses the protected mode of the 80286 microprocessor to
  339.  unleash the 16-MB address space of the 80286 and implement efficient and
  340.  safe multitasking.
  341.  
  342.  The introduction of OS/2 is a critical turning point for the entire industry
  343.  that has grown up around the IBM PC. MS-DOS has proved unable to satisfy the
  344.  growing needs of users and program developers. To be tolerable, MS-DOS now
  345.  requires various add-on kludges such as bank-switched memory or control
  346.  programs based on the virtual-8086 mode of the 80386 microprocessor. OS/2
  347.  and the Presentation Manager give the IBM PC industry the opportunity to
  348.  pull free of the MS-DOS quagmire and take a major step forward. Some people
  349.  at Microsoft say that OS/2 will establish the foundations of PC operating
  350.  systems for the next decade. That's a gutsy prediction. But considering that
  351.  MS-DOS has lasted seven years already, it's really not so difficult to
  352.  believe.
  353.  
  354.  The OS/2 Kernel
  355.  
  356.  The initial version of OS/2 (OS/2 1.0), often called the OS/2 kernel, has
  357.  been available to programmers since June 1987. Microsoft released version
  358.  1.0 to original equipment manufacturers (OEMs) in December 1987, and IBM
  359.  released it for retail sale the same month.
  360.  
  361.  The OS/2 kernel is a traditional environment for both users and programmers.
  362.  The command line interface and most internal and external commands have been
  363.  inherited from MS-DOS. From the programmer's perspective, the functionality
  364.  of the kernel resembles MS-DOS, UNIX, and traditional minicomputer operating
  365.  systems. The kernel handles file I/O, memory management, and multitasking.
  366.  The API includes facilities for keyboard and mouse input and a fast
  367.  full-screen character-mode video I/O (VIO) system.
  368.  
  369.  The OS/2 kernel supports multiple full-screen sessions (sometimes also
  370.  called "screen groups"). Each session runs one or more processes that use
  371.  the video display in either a teletype or full-screen fashion. A user can
  372.  switch between sessions by pressing the Alt-Esc key combination. One session
  373.  is the MS-DOS compatibility mode session, which uses the real mode of the
  374.  80286 microprocessor to run most existing programs written for MS-DOS.
  375.  
  376.  The OS/2 Presentation Manager
  377.  
  378.  The Presentation Manager is part of OS/2 version 1.1, released in the last
  379.  quarter of 1988. In OS/2 1.1, one session runs in a graphics mode and is
  380.  devoted to the Presentation Manager. All Presentation Manager applications
  381.  (as well as a Task Manager and Start Programs window that are part of the
  382.  Presentation Manager shell) run in this session. The addition of the
  383.  Presentation Manager to OS/2 requires little in the way of changes to the
  384.  OS/2 kernel. Instead, the Presentation Manager is basically a collection of
  385.  dynamic link libraries (.DLL files) that extend the functionality of OS/2 to
  386.  include window management and graphics.
  387.  
  388.  Although the Presentation Manager session is primarily for Presentation
  389.  Manager programs, many programs written for the OS/2 kernel can also run in
  390.  "text windows" in this session. However, these programs can't use graphics
  391.  or take advantage of menus, dialog boxes, and other aspects of the user
  392.  interface. OS/2 kernel programs that write directly to the video display or
  393.  that install video, keyboard, or mouse subsystems are prohibited from
  394.  running under the Presentation Manager. These programs must continue to run
  395.  in their own sessions.
  396.  
  397.  Freedom of Choice
  398.  
  399.  Programmers have a choice of developing applications for either the OS/2
  400.  kernel or the OS/2 Presentation Manager. Each environment has distinct
  401.  advantages and disadvantages.
  402.  
  403.  For some applications, the OS/2 kernel is obviously preferable. For example,
  404.  an existing MS-DOS character-mode text editor or word processor that is
  405.  known for its speed should probably be ported to the OS/2 kernel rather than
  406.  to the Presentation Manager. Because the Presentation Manager runs in a
  407.  graphics mode, a Presentation Manager version of the program will run more
  408.  slowly with existing video display adapters. The kernel is also a better
  409.  choice for developers who have designed a unique and well-known user
  410.  interface for their MS-DOS programs and feel reluctant to abandon it.
  411.  
  412.  Developers who want to port their MS-DOS programs to OS/2 as quickly as
  413.  possible will find the kernel to be an easier path. Presentation Manager
  414.  programs are more difficult to develop and debug than traditionally
  415.  structured programs. Porting an existing MS-DOS program to the Presentation
  416.  Manager often requires turning the program inside out to accommodate the
  417.  Presentation Manager architecture.
  418.  
  419.  But for many sophisticated applications──particularly those that use
  420.  graphics──the Presentation Manager is clearly the better environment.
  421.  Let's see why.
  422.  
  423.  The Graphical Environment
  424.  
  425.  The proof is in the programs. Two of the more interesting MS-DOS
  426.  applications released in the past couple of years are Microsoft Excel and
  427.  Aldus PageMaker, both of which run under Microsoft Windows. That both of
  428.  these programs were originally developed for the Apple Macintosh indicates
  429.  how a graphical windowing environment can inspire program developers to
  430.  create a radically new and exciting variation of an older concept (in the
  431.  case of Microsoft Excel) and even to create a whole new class of software
  432.  (in the case of PageMaker). The graphical environment of the Presentation
  433.  Manager is rich in functionality──programs can use graphics and formatted
  434.  text to convey a high density of information to the user.
  435.  
  436.  A traditional program gets user input from the keyboard and displays output
  437.  to the screen. But with the addition of a mouse, the screen itself becomes a
  438.  potential source of user input. Logic within the Presentation Manager
  439.  assists the application in obtaining user input from various controls on the
  440.  screen, such as menus, scroll bars, buttons, and dialog boxes. The
  441.  interaction between the mouse and the screen narrows the gap between user
  442.  and program.
  443.  
  444.  The Consistent User Interface
  445.  
  446.  Because the menu and dialog box interface is built into the Presentation
  447.  Manager rather than into each individual application, the interface is
  448.  consistent across applications. This means that a user with experience with
  449.  one Presentation Manager program (or with Microsoft Windows) can easily
  450.  learn a new Presentation Manager program. For example, the first time I saw
  451.  a beta version of Microsoft Excel for Windows, I had no documentation, no
  452.  help files, and no experience with the Macintosh version of Microsoft Excel.
  453.  But I did have experience with other Windows programs. I knew how the menus
  454.  and dialog boxes worked, and I was able to quickly learn much of Microsoft
  455.  Excel solely by experimentation.
  456.  
  457.  Some people fear that a system such as the Presentation Manager will lead to
  458.  an undesirable uniformity of programs. Every program will look like every
  459.  other program, they say, and designer creativity will be inhibited. To
  460.  counter this view, the best examples are, again, PageMaker and Microsoft
  461.  Excel. Although the menus and dialog boxes are certainly the most obvious
  462.  aspects of the user interface, much more important interaction between the
  463.  user and program occurs within the window itself. The programmer is
  464.  liberated from worrying about the mundane aspects of the user interface and
  465.  is free to spend more time where it really counts.
  466.  
  467.  Device-independent Graphics
  468.  
  469.  The IBM PC was designed around the principle of open architecture.
  470.  Third-party manufacturers have responded to this fact by developing many
  471.  different──and often incompatible──graphics output devices. Under
  472.  MS-DOS, program developers have faced the problem of writing their own
  473.  device drivers for the CGA, the Hercules Graphics Card, the EGA, and the
  474.  VGA, as well as for a number of high-resolution video adapters. The problem
  475.  of printers is even worse: Some MS-DOS word-processing packages include one
  476.  or two disks containing nothing but small files, each supporting a different
  477.  printer.
  478.  
  479.  With the Presentation Manager, this all goes away. The Graphics Programming
  480.  Interface (GPI) of the Presentation Manager is device independent. An
  481.  application need not identify the output device in order to use it. If a
  482.  Presentation Manager driver exists for the output device, then all
  483.  Presentation Manager programs can use the device. This also helps to protect
  484.  programs from obsolescence. Video technology is advancing very quickly, but
  485.  Presentation Manager programs written today will run without change on the
  486.  video adapters of the future.
  487.  
  488.  The SAA Future
  489.  
  490.  Aside from their important role in OS/2, the Presentation Manager user
  491.  interface and API are also part of IBM's ambitious Systems Application
  492.  Architecture (SAA). SAA attempts to correct a historical weakness in IBM's
  493.  line of computers and operating systems by setting user interface and API
  494.  standards. The Presentation Manager is one of the first products to be a
  495.  part of SAA. If the goals of SAA come to pass, then the Presentation Manager
  496.  user interface will become a common sight on IBM minicomputer and mainframe
  497.  terminals. Just as important for the program developer, it may one day be
  498.  possible to write a Presentation Manager program in a high-level language
  499.  and compile it to run on a variety of computers from the IBM AT to the IBM
  500.  370.
  501.  
  502.  Of course, this isn't going to happen next month or even the month after
  503.  that. Porting Presentation Manager programs to other operating systems
  504.  involves problems that PC programmers usually don't need to worry about
  505.  (such as filenames over 12 characters in length) and problems PC programmers
  506.  usually wish they didn't need to worry about (such as the segmented
  507.  architecture of Intel microprocessors). Nonetheless, SAA indicates the
  508.  potential importance of the Presentation Manager in the future of the
  509.  personal computer and the not-quite-personal computers as well.
  510.  
  511.  
  512.  Presentation Manager Programming
  513.  
  514.  At first glance, a typical Presentation Manager program seems to be written
  515.  in an unfamiliar programming language. The programs are full of uppercase
  516.  identifiers and variable types, strange-looking variable names, nested
  517.  switch statements, and many calls to Presentation Manager functions. Those
  518.  odd-looking Presentation Manager programs are usually written in C. Although
  519.  it is possible to use other languages, C will probably remain the preferred
  520.  language for Presentation Manager programming, largely because of its
  521.  flexibility in pointer and structure manipulation. If you don't know C,
  522.  programming for the Presentation Manager is probably not a good place to
  523.  start learning the language. I recommend you learn C by programming for a
  524.  more traditional environment, such as the OS/2 kernel. If your C is a little
  525.  rusty, brushing up on structures and pointers is a must.
  526.  
  527.  The Header Files
  528.  
  529.  C programs for the Presentation Manager require the use of header files
  530.  supplied with the Microsoft OS/2 Programmer's Toolkit. These are the header
  531.  files used in OS/2 kernel and Presentation Manager programs:
  532.  
  533.     Header File   Description
  534.     OS2.H         Includes OS2DEF.H, BSE.H, and PM.H
  535.     OS2DEF.H      Common type and macro definitions
  536.     BSE.H         Includes BSEDOS.H, BSESUB.H, and BSEERR.H
  537.     BSEDOS.H      Dos functions and structures
  538.     BSESUB.H      Vio, Mou, and Kbd functions and structures
  539.     BSEERR.H      Dos, Vio, Mou, and Kbd error codes
  540.     PM.H          Includes PMWIN.H, PMGPI.H, PMDEV.H, PMAVIO.H, and PMSPL.H
  541.     PMWIN.H       Most Win functions and structures; and includes PMSHL.H
  542.     PMSHL.H       Win functions for session manager shell
  543.     PMGPI.H       Gpi functions and structures
  544.     PMDEV.H       Dev functions and structures
  545.     PMAVIO.H      Vio functions for Advanced VIO interface
  546.     PMSPL.H       Spl functions and structures
  547.  
  548.  These header files are an important part of Presentation Manager
  549.  documentation. You'll want to print out a copy for reference.
  550.  
  551.  Many Presentation Manager functions require numeric constants as parameters.
  552.  You rarely need to remember the actual values of these constants, because
  553.  the header files contain hundreds of #define statements that define
  554.  identifiers for the constants. These identifiers are in uppercase letters.
  555.  Most begin with a two-letter, three-letter, or four-letter prefix that
  556.  indicates a general group of identifiers. The header files also define
  557.  identifiers for most of the data types you use in your Presentation Manager
  558.  programs, as well as numerous data structures used in passing information
  559.  between the application and the Presentation Manager. I'll discuss these as
  560.  we encounter them in the chapters ahead.
  561.  
  562.  Programmers working with the Presentation Manager often find helpful a
  563.  convention for naming variables that is known as "Hungarian notation," in
  564.  honor of its inventor, the legendary Microsoft programmer Charles Simonyi.
  565.  This convention adds a lowercase abbreviation of the data type to the
  566.  beginning of the variable name. Again, I'll discuss this system in context
  567.  as we begin writing Presentation Manager programs.
  568.  
  569.  All OS/2 and Presentation Manager functions available to an application are
  570.  declared in the header files. These function declarations provide type
  571.  checking during compilation. In some cases, the function templates also help
  572.  with pointer conversions. For example, whenever a pointer is passed as a
  573.  parameter to an OS/2 function, it must be a far (or long) 32-bit pointer.
  574.  However, you usually don't need to explicitly cast near (or short) 16-bit
  575.  pointers to far pointers. The function template in the header file lets the
  576.  compiler do this for you.
  577.  
  578.  The OS/2 functions always begin with a three-letter prefix that identifies a
  579.  large group of functions. The header files are generally organized around
  580.  these groups of functions:
  581.  
  582.     Prefix      Function Group
  583.     Dos         Kernel file I/O, memory management, and tasking
  584.     Vio         Video I/O
  585.     Kbd         Keyboard input in kernel programs
  586.     Mou         Mouse input in kernel programs
  587.     Win         Presentation Manager windowing and user interface
  588.     Gpi         Presentation Manager Graphics Programming Interface
  589.     Dev         Presentation Manager device context interface
  590.     Spl         Presentation Manager print spooler
  591.  
  592.  The Kbd and Mou functions aren't used at all in Presentation Manager
  593.  programs. Vio functions are used only in a Presentation Manager output
  594.  system called "Advanced VIO," which I discuss in Chapter 7.
  595.  
  596.  Message-based Architecture
  597.  
  598.  Most traditional operating systems provide a set of functions that a program
  599.  calls for various system services. That is still the case in the
  600.  Presentation Manager, but a Presentation Manager program also gets
  601.  information from the operating system in a very different way──through
  602.  "messages." For example, in an OS/2 kernel program you use Kbd and Mou
  603.  functions to obtain keyboard and mouse input. In the Presentation Manager, a
  604.  program obtains keyboard and mouse input through messages that the
  605.  Presentation Manager sends to the program.
  606.  
  607.  But it's not only simple keyboard and mouse input that is delivered to a
  608.  program in the form of messages. Messages also inform a program when a user
  609.  has selected an item from a menu, when the program's window has been
  610.  resized, and even when the program should repaint part of its window. In
  611.  fact, Presentation Manager programs are largely message-driven. A program
  612.  remains dormant most of the time until it receives a message; it thus does
  613.  little but process messages. Coming to terms with this message architecture
  614.  is a major hurdle of learning to program for the Presentation Manager. But
  615.  don't worry about understanding this architecture right off the bat. We'll
  616.  spend most of this book learning how to process messages.
  617.  
  618.  
  619.  A Note to Windows Programmers
  620.  
  621.  If you have experience with programming for Microsoft Windows, you're
  622.  already several steps ahead of everybody else in mastering the Presentation
  623.  Manager. But don't feel too complacent. The major concepts are the same, but
  624.  the details are different. For example, right now you're familiar with a
  625.  program's "client area." In the Presentation Manager we speak instead of a
  626.  "client window." All parts of the window that are "nonclient areas" under
  627.  Windows are separate windows in the Presentation Manager.
  628.  
  629.  I found it relatively easy to go from Windows programming to Presentation
  630.  Manager programming. I also found it easy (in most cases) to convert
  631.  existing Windows programs to the Presentation Manager API. The best news for
  632.  Windows programmers, however, is that OS/2 is a more hospitable environment
  633.  for a windowing and multitasking system. Under MS-DOS, Windows outclasses
  634.  the operating system and has to compensate for the weaknesses in MS-DOS.
  635.  Windows is like stained-glass artwork in the wall of a log cabin. Under
  636.  OS/2, Windows (in the form of the Presentation Manager) has finally found
  637.  its proper home.
  638.  
  639.  
  640.  Easy or Hard?
  641.  
  642.  Microsoft Windows has the reputation of being a difficult system for
  643.  programmers to learn, and it's likely that the Presentation Manager will
  644.  gain the same reputation. I've already spoken of the hurdle of moving from a
  645.  traditional operating system to a message-based architecture. That's part of
  646.  the problem. The steep learning curve also results from the sheer bulk of
  647.  Presentation Manager function calls (about 500 of them). But what's the
  648.  alternative? Would you rather learn how to use the menu logic built into the
  649.  Presentation Manager, or would you prefer to write your own menu routines?
  650.  Would you rather learn how to draw circles using GPI functions, or would you
  651.  prefer to write your own circle-drawing routines and adapt them for every
  652.  video adapter and printer your program may encounter?
  653.  
  654.  Out of necessity, application programs have become more complex in the past
  655.  few years, because the programs have been made easier to operate for naive
  656.  users and, at the same time, more powerful for sophisticated users. As the
  657.  user base expands to encompass less sophisticated users, the applications,
  658.  application program interfaces, and programmers must become more
  659.  sophisticated. Program developers can no longer require users to spend many
  660.  hours reading manuals before they begin to use an application. The
  661.  application's interface must be obvious and intuitively clear. By
  662.  programming for the Presentation Manager, you begin with an interface that
  663.  is already familiar to the user. In short, learning to program for the
  664.  Presentation Manager may be hard, but it's easier than the alternative.
  665.  
  666.  So enough of this. Let's start pounding out some code.
  667.  
  668.  
  669.  Chapter 2  Welcome to Presentation Manager Programming
  670.  ───────────────────────────────────────────────────────────────────────────
  671.  
  672.  
  673.  Books that teach you how to program in C often begin with a "do-nothing"
  674.  program and proceed quickly to the traditional "Hello world" program. The
  675.  Presentation Manager analogue of the "Hello world" program isn't quite as
  676.  straightforward, so we'll spend this entire chapter creating it. We'll begin
  677.  with a "do-nothing" program called W and progressively build it into a
  678.  program called WELCOME1 that creates a window, displays a message in it, and
  679.  (as a bonus) plays a little music.
  680.  
  681.  
  682.  W──The Do-Nothing Program
  683.  
  684.  A Presentation Manager program is usually constructed from several files.
  685.  Figure 2-1 shows the three files that make up the W program:
  686.  
  687.    ■  W (a make file)
  688.    ■  W.C (a program source code file)
  689.    ■  W.DEF (a module definition file)
  690.  
  691.  As you will see, these three types of files are normal for all Presentation
  692.  Manager programs.
  693.  
  694.  Because the W program itself does nothing interesting, we'll instead take a
  695.  moment to examine the mechanics of compiling and linking a Presentation
  696.  Manager program.
  697.  
  698.  
  699.  Figure 2-1.  The W program.
  700.  
  701.    The W File
  702.  
  703.    #-------------
  704.    # W make file
  705.    #-------------
  706.  
  707.    w.obj : w.c
  708.         cl -c -G2 -W3 w.c
  709.  
  710.    w.exe : w.obj w.def
  711.         link w, /align:16, NUL, os2, w
  712.  
  713.    The W.C File
  714.  
  715.    /*-----------------------------
  716.       W.C -- A Do-Nothing Program
  717.      -----------------------------*/
  718.  
  719.    int main (void)
  720.         {
  721.         return 0 ;
  722.         }
  723.  
  724.    The W.DEF File
  725.  
  726.    ;------------------------------
  727.    ; W.DEF module definition file
  728.    ;------------------------------
  729.  
  730.    NAME           W         WINDOWCOMPAT
  731.  
  732.    DESCRIPTION    'Welcome to PM -- Program No. 1 (C) Charles Petzold, 1988'
  733.    PROTMODE
  734.    HEAPSIZE       1024
  735.    STACKSIZE      2048
  736.  
  737.  
  738.  The Make File
  739.  
  740.  The first file is a "make file" named W. A make file is a text file that
  741.  contains a series of commands to create a .EXE (executable) file from one or
  742.  more source code files.
  743.  
  744.  By convention, a make file is given the same name as the program it creates
  745.  but with no extension. The MAKE.EXE program that is included with the
  746.  Microsoft C compiler reads this file and compares the date and time of the
  747.  "target" file (to the left of a colon) with the date and time of the
  748.  "dependent" file or files (to the right of the colon). If any dependent file
  749.  has been changed more recently than the target file, the indented commands
  750.  that follow are run. In the W make file, the C compiler (CL──the .EXE
  751.  extension is assumed) is run if the W.C source code file is more recent than
  752.  the W.OBJ object file. The linker (LINK──the .EXE extension is assumed) is
  753.  run if W.OBJ or W.DEF is more recent than W.EXE. Besides simplifying the
  754.  creation of .EXE files, the make file also serves as a form of documentation
  755.  about the program. It shows the various modules that contribute to the
  756.  program and how they are combined into an executable file.
  757.  
  758.  Assuming you have the OS/2 C compiler and associated files properly
  759.  installed, you can create W.EXE from W, W.C, and W.DEF by running the MAKE
  760.  program on the OS/2 CMD.EXE command line (either in a full-screen
  761.  character-mode session or running in a window in the Presentation Manager):
  762.  
  763.    MAKE W
  764.  
  765.  If MAKE, CL, or LINK reports errors, your system is probably not set up
  766.  correctly. You should have the OS/2 C compiler, LINK, and MAKE accessible
  767.  through a directory listed in your PATH environment variable and the C and
  768.  OS/2 .LIB files in a directory listed in your LIB environment variable. In
  769.  particular, LINK needs to find the SLIBCE.LIB library file. (I explain in
  770.  the preface how to install Microsoft C 5.1 so that this file exists.)
  771.  
  772.  Compiling
  773.  The following command line in the W make file compiles the W.C source code
  774.  file, creating the W.OBJ object code file:
  775.  
  776.    cl -c -G2 -W3 w.c
  777.  
  778.  The switches used in this compilation are as follows:
  779.  
  780.  The -c switch causes the C compiler to compile the program but not to link
  781.  it. The link is the second step in the make file.
  782.  
  783.  The -G2 switch generates 80286 code during the compilation. Because the OS/2
  784.  Presentation Manager runs only on an Intel 80286 or 80386 microprocessor,
  785.  you should always use this switch. It creates smaller and faster programs.
  786.  
  787.  The -W3 switch stands for "warning level 3" and causes the C compiler to
  788.  display additional warning messages about potential problems in your
  789.  programs. This becomes particularly important when the program makes calls
  790.  to OS/2 or Presentation Manager functions. You should set as a goal that all
  791.  your Presentation Manager programs compile without any warning messages when
  792.  the -W3 switch is used.
  793.  
  794.  Linking
  795.  If the compilation is successful, the following command in W links the
  796.  W.OBJ file to create an executable W.EXE file:
  797.  
  798.    link w, /align:16, NUL, os2, w
  799.  
  800.  The first parameter to LINK.EXE is the name of the W.OBJ object code file.
  801.  The .OBJ extension is assumed.
  802.  
  803.  The second parameter is the name of the .EXE file. If this name isn't
  804.  explicitly listed (as it isn't here), LINK uses the name of the first .OBJ
  805.  file and adds a .EXE extension. The /align:16 switch aligns segments in the
  806.  .EXE file on 16-byte boundaries. By default, LINK aligns segments on
  807.  512-byte boundaries. For programs with small code and data segments, this
  808.  switch can appreciably reduce the size of the .EXE file.
  809.  
  810.  The third parameter to LINK is the name of a map file. Specifying NUL
  811.  prevents the map file from being created.
  812.  
  813.  The fourth parameter lists the names of the libraries to be linked with the
  814.  .OBJ file. (LINK also uses the C run-time library SLIBCE.LIB, but because
  815.  the C compiler embeds this name in the .OBJ file, you don't have to list it
  816.  in the LINK step.) OS2.LIB is an "import library" for OS/2 functions. This
  817.  file allows LINK to construct the .EXE file so that it contains dynamic link
  818.  information. When you run an OS/2 program, OS/2 uses this information in the
  819.  .EXE files to link calls to OS/2 functions within the program with the
  820.  functions themselves. Although W doesn't seem to make any OS/2 function
  821.  calls, the start-up code makes a few. The presence of these imported
  822.  functions causes LINK to create a .EXE file in the "New Executable" format,
  823.  which is the OS/2 .EXE format.
  824.  
  825.  The fifth parameter to LINK is the name of the program's "module definition
  826.  file," W.DEF. The .DEF extension is assumed.
  827.  
  828.  The Module Definition File
  829.  
  830.  Although it isn't strictly required for this simple do-nothing program,
  831.  Presentation Manager applications usually require a "module definition
  832.  file." This is a simple text file that LINK uses when constructing the
  833.  program's .EXE file. The module definition file commonly has the same name
  834.  as the program, but with a .DEF extension.
  835.  
  836.  The W.DEF file shown in Figure 2-1 begins with a NAME statement. This
  837.  identifies the module as a program (rather than a dynamic link library) and
  838.  gives it a module name of W. This should be the same name as the program's
  839.  .EXE file. The keyword WINDOWCOMPAT causes LINK to set a flag in the W.EXE
  840.  file. This flag tells OS/2 that although the program is not a Presentation
  841.  Manager program, it can be run in a text window within the Presentation
  842.  Manager session.
  843.  
  844.  The text in the DESCRIPTION line is embedded by LINK in the header section
  845.  of the .EXE file. This is an excellent place for a copyright notice or other
  846.  information about the program.
  847.  
  848.  The PROTMODE keyword indicates that the program will be run only in OS/2
  849.  protected mode. This often allows LINK to shorten the .EXE file.
  850.  
  851.  The HEAPSIZE statement specifies an initial size of memory to be used for a
  852.  local heap. The local heap is located in the program's automatic data
  853.  segment. C library functions (such as malloc) and some Presentation Manager
  854.  functions let you allocate memory from this heap.
  855.  
  856.  The STACKSIZE statement specifies the size of the program's stack. The
  857.  recommended minimum stack size for OS/2 programs is 2 KB. The stack size for
  858.  Presentation Manager programs that create windows is 8 KB, so we'll use a
  859.  larger STACKSIZE later in this chapter.
  860.  
  861.  We'll add another line to the module definition file before this chapter is
  862.  completed, but the general information shown in the W.DEF file will remain
  863.  about the same for most programs in this book.
  864.  
  865.  Running W.EXE
  866.  
  867.  After creating W.EXE, you can run the program in a variety of ways, most
  868.  easily by executing it from the OS/2 CMD.EXE prompt, either in a full-screen
  869.  character-mode session or in a Presentation Manager window. You can also run
  870.  the program from the File System or install it to be run from the Start
  871.  Programs window. When installing it in the Start Programs window, specify
  872.  that it is not a Presentation Manager program. If you run W.EXE from the
  873.  File System or Start Programs window, a text window is briefly created for
  874.  it and then destroyed as the program terminates.
  875.  
  876.  
  877.  WE ── Obtaining an Anchor Block Handle
  878.  
  879.  A Presentation Manager program makes many calls to Presentation Manager
  880.  functions. But the very first Presentation Manager function that the program
  881.  must call is WinInitialize. This function registers the program with the
  882.  system and returns the "anchor block handle." (The term "anchor block" has
  883.  origins in the mainframe world but has no significant meaning in the context
  884.  of OS/2 or the Presentation Manager.) Before the program terminates, it
  885.  should call WinTerminate to free the anchor block handle. The WE program in
  886.  Figure 2-2 shows how this is done. WE is still basically a do-nothing
  887.  program, but it's now a do-nothing program that can use some Presentation
  888.  Manager functions.
  889.  
  890.  Figure 2-2.  The WE program.
  891.  
  892.    The WE File
  893.  
  894.    #--------------
  895.    # WE make file
  896.    #--------------
  897.  
  898.    we.obj : we.c
  899.         cl -c -G2 -W3 we.c
  900.  
  901.    we.exe : we.obj we.def
  902.         link we, /align:16, NUL, os2, we
  903.  
  904.    The WE.C File
  905.  
  906.    /*-------------------------------------------------------
  907.       WE.C -- A Program that Obtains an Anchor Block Handle
  908.      -------------------------------------------------------*/
  909.  
  910.    #include <os2.h>
  911.  
  912.    int main (void)
  913.         {
  914.         HAB  hab ;
  915.  
  916.         hab = WinInitialize (0) ;
  917.  
  918.         WinTerminate (hab) ;
  919.         return 0 ;
  920.         }
  921.  
  922.    The WE.DEF File
  923.  
  924.    ;-------------------------------
  925.    ; WE.DEF module definition file
  926.    ;-------------------------------
  927.  
  928.    NAME           WE        WINDOWCOMPAT
  929.  
  930.    DESCRIPTION    'Welcome to PM -- Program No. 2 (C) Charles Petzold, 1988'
  931.    PROTMODE
  932.    HEAPSIZE       1024
  933.    STACKSIZE      2048
  934.  
  935.  
  936.  You can create WE.EXE from the three files by executing
  937.  
  938.    MAKE WE
  939.  
  940.  You can run WE.EXE in the same way you run W.EXE. The program still doesn't
  941.  do much of anything.
  942.  
  943.  In going from W to WE, the changes made to the three standard files at first
  944.  look innocuous. But you'll find when creating WE.EXE that the compilation
  945.  takes a little longer than it did previously. It's almost as if the compiler
  946.  has to digest several other files in addition to WE.C. As you'll see in the
  947.  following discussion, that's exactly the case.
  948.  
  949.  The Header Files
  950.  
  951.  Near the top of WE.C is the preprocessor statement:
  952.  
  953.    #include <os2.h>
  954.  
  955.  OS2.H is a master header file that contains other #include statements for
  956.  all other OS/2 and Presentation Manager header files. All of these header
  957.  files should be located in a subdirectory listed in your INCLUDE environment
  958.  string. These header files are extremely important, and you should treat
  959.  them as primary documentation for the Presentation Manager, as I mentioned
  960.  in Chapter 1. Even for a program as simple as WE, these header files supply
  961.  function declarations and definitions of identifiers used in the program.
  962.  Let's examine how the header files affect the compilation of WE.C.
  963.  
  964.  The WE.C program defines one variable (hab) and calls two Presentation
  965.  Manager functions, WinInitialize and WinTerminate. These two functions are
  966.  located in the PMWIN.DLL dynamic link library that OS/2 links your program
  967.  with when you run the program. The hab variable is defined within the main
  968.  function of WE.C:
  969.  
  970.    HAB  hab ;
  971.  
  972.  The data type of hab is HAB, which stands for "handle to an anchor block."
  973.  (I'll discuss what a handle is shortly.) This HAB type is defined by a
  974.  typedef statement in OS2DEF.H:
  975.  
  976.  typedef LHANDLE HAB
  977.  
  978.  The LHANDLE data type is defined like this:
  979.  
  980.    typedef void far *LHANDLE ;
  981.  
  982.  Thus the C compiler will treat the variable hab as a 32-bit far pointer.
  983.  
  984.  The WinInitialize and WinTerminate functions are declared in PMWIN.H:
  985.  
  986.    HAB  APIENTRY WinInitialize (USHORT) ;
  987.    BOOL APIENTRY WinTerminate (HAB hab) ;
  988.  
  989.  BOOL and USHORT are data types defined in OS2DEF.H:
  990.  
  991.    typedef unsigned short BOOL ;
  992.    typedef unsigned short USHORT
  993.  
  994.  Thus the WinInitialize function takes an unsigned short parameter and
  995.  returns a 32-bit value of type HAB, a handle to an anchor block. The
  996.  WinTerminate function accepts an anchor block handle as a parameter and
  997.  returns an unsigned short. The program treats this return value as a BOOL,
  998.  which is a data type that is either 0 or 1.
  999.  
  1000.  The APIENTRY identifier is also defined in OS2DEF.H:
  1001.  
  1002.    #define APIENTRY pascal far
  1003.  
  1004.  This indicates that the two functions are far functions (that is, the
  1005.  compiler must generate a far, or intersegment, call to these functions when
  1006.  compiling the program) and that they have a "Pascal" calling sequence. Using
  1007.  the Pascal calling sequence tells the C compiler two important facts about
  1008.  the function:
  1009.  
  1010.    ■  The parameters to the function are pushed on the stack from left to
  1011.       right, rather than from right to left as is normal with C functions.
  1012.  
  1013.    ■  The function itself adjusts the stack to remove the parameters. When
  1014.       the function returns to the program, the parameters have already been
  1015.       removed.
  1016.  
  1017.  For the Intel 8086 family of microprocessors, the Pascal calling sequence is
  1018.  slightly faster and more efficient than the C calling sequence. Because all
  1019.  Presentation Manager functions are far functions that use the Pascal calling
  1020.  sequence, they are all declared in the header files with the APIENTRY
  1021.  identifier.
  1022.  
  1023.  If you were to write WE.C without using the header files or any #define or
  1024.  typedef statements, it would look like this:
  1025.  
  1026.    void far * pascal far WinInitialize (unsigned short) ;
  1027.    unsigned short pascal far WinTerminate (void far *) ;
  1028.  
  1029.    int main (void)
  1030.         {
  1031.         void far *hab ;
  1032.  
  1033.         hab = WinInitialize (0) ;
  1034.  
  1035.         WinTerminate (hab) ;
  1036.         return 0 ;
  1037.         }
  1038.  
  1039.  In one sense, this is easier to read, because it uses only data types that
  1040.  are understood by the Microsoft C Compiler. However, in many ways this
  1041.  version is much more obscure than the version that uses the Presentation
  1042.  Manager header files.
  1043.  
  1044.  For example, the WinTerminate function is declared in PMWIN.H as returning a
  1045.  BOOL (Boolean value), indicating that the function returns a 0 if the
  1046.  function fails and a 1 if it succeeds. This fact could be important, and yet
  1047.  it's not at all intuitive if the WinTerminate function is declared as
  1048.  returning an unsigned short. Likewise, the return value of WinInitialize
  1049.  isn't just any old far pointer──it's a handle to an anchor block. It's not
  1050.  even important for you to know that an anchor block handle is really a far
  1051.  pointer. All you need to know is that it's an anchor block handle. You
  1052.  should use this value only in other functions that accept an anchor block
  1053.  handle as a parameter, such as WinTerminate.
  1054.  
  1055.  Although the far and pascal keywords are supported in Microsoft C, you
  1056.  should keep in the back of your mind the possibility of one day recompiling
  1057.  your Presentation Manager programs to run on systems other than the PC.
  1058.  Because the far keyword is necessary only because of the segmented
  1059.  architecture of the 8086 family of microprocessors, this keyword probably
  1060.  isn't supported in C compilers on other systems. For this reason, many of
  1061.  the more machine-specific keywords in Microsoft C are redefined with
  1062.  uppercase names, like this:
  1063.  
  1064.    #define FAR    far
  1065.    #define PASCAL pascal
  1066.  
  1067.  If you need to use the far or pascal keywords in your program, use these
  1068.  uppercase identifiers instead. This allows you to more easily port your
  1069.  programs to another system, because you'll recompile the program with
  1070.  different header files that define the identifiers as appropriate for the
  1071.  system.
  1072.  
  1073.  The Proper Handling of Handles
  1074.  
  1075.  When you program for the Presentation Manager, you're really engaged in a
  1076.  form of "object-oriented programming." Many Presentation Manager functions
  1077.  obtain information about an object, act on an object, or cause an object to
  1078.  act on itself. A "handle" is a number that refers to an object. Almost every
  1079.  Presentation Manager function call──one exception is WinInitialize──
  1080.  requires a handle as the first parameter.
  1081.  
  1082.  ───────────────────────────────────────────────────────────────────────────
  1083.  NOTE:
  1084.     Although this requirement implies that every Presentation Manager
  1085.     function acts on an object, this is really not the case. Presentation
  1086.     Manager function calls require a handle as the first parameter because of
  1087.     the requirements of IBM's Systems Application Architecture (SAA), of
  1088.     which the Presentation Manager is a part. As you'll see, some functions
  1089.     really don't need a handle to anything. These functions sometimes require
  1090.     the anchor block handle as the first parameter.
  1091.  ───────────────────────────────────────────────────────────────────────────
  1092.  
  1093.  The concept of a handle shouldn't be new to you. If you've done
  1094.  assembly-language programming under MS-DOS or the OS/2 kernel, or if you've
  1095.  ever used the C file I/O functions open, read, write, and close, you're
  1096.  familiar with file handles. Under the OS/2 kernel, a program can obtain a
  1097.  file handle from the DosOpen function call. The open file is an object. The
  1098.  file handle refers to this object. You use the handle when calling DosRead,
  1099.  DosWrite, or other functions that act on the open file. You eventually close
  1100.  the file using DosClose. After the DosClose call, the file handle is
  1101.  invalid. Although the file handle is a number, the actual value of the
  1102.  handle returned from DosOpen isn't important to your program. The value of
  1103.  the file handle is meaningful only to the OS/2 kernel. Obviously, the OS/2
  1104.  kernel maintains a table of open files, and the file handle somehow
  1105.  references that table. But your program doesn't need to know this. OS/2
  1106.  hides this data from your program.
  1107.  
  1108.  The handles you use in the Presentation Manager are similar to file handles.
  1109.  But in the Presentation Manager almost everything has a handle. Before we're
  1110.  finished with this chapter, we'll have encountered a number of them:
  1111.  
  1112.    ■  Anchor block handles
  1113.    ■  Message queue handles
  1114.    ■  Window handles
  1115.    ■  System mouse pointer handles
  1116.    ■  Presentation space handles
  1117.  
  1118.  Every handle is obtained from a Presentation Manager function. You save the
  1119.  handle in a variable. You then use this handle in other Presentation Manager
  1120.  functions. At some point, you usually call a function that destroys the
  1121.  resources connected with the handle. At that time the handle becomes
  1122.  invalid.
  1123.  
  1124.  Most handles are 32 bits long, but some are 16 bits long. Often, handles are
  1125.  actually addresses to structures that are maintained internally by the
  1126.  Presentation Manager. But your program doesn't access these structures
  1127.  directly. You don't even have to know which handles are 32 bits long and
  1128.  which are 16 bits long, because you use the data types defined in the header
  1129.  files (such as HAB) to define variables to store the handles.
  1130.  
  1131.  A handle with a value of 0 is called a NULL handle. (NULL is defined in
  1132.  OS2DEF.H as 0.) Just as in C programming, where a NULL pointer is often an
  1133.  invalid pointer, in Presentation Manager programming a NULL handle returned
  1134.  from a function is usually an indication of an error. In some cases,
  1135.  however, you can use a NULL handle as a default parameter to a function that
  1136.  requires a handle. We'll examine these cases as they arise.
  1137.  
  1138.  The anchor block handle is a peculiar handle. I've already mentioned that
  1139.  handles refer to objects. The object to which the anchor block handle refers
  1140.  is the program itself──the program that calls WinInitialize. Let's be more
  1141.  precise. What we call a program is usually the .EXE file. But the program
  1142.  can be run multiple times. While a particular instance of a program is
  1143.  running, it is called a process. The anchor block handle refers to the
  1144.  particular process that calls WinInitialize.
  1145.  
  1146.  Usually, a Presentation Manager program calls WinInitialize when it begins
  1147.  execution, so the program can then call other Presentation Manager
  1148.  functions. Right before the program is ready to terminate, it pulls up its
  1149.  anchor with WinTerminate and departs.
  1150.  
  1151.  Running WE.EXE
  1152.  
  1153.  I have some bad news for you. Although we are gathered here to write
  1154.  Presentation Manager programs, we're not there yet. You might think that
  1155.  calling the magic function WinInitialize turns an ordinary OS/2 program into
  1156.  a Presentation Manager program, but it's not so. Like W.EXE, WE.EXE is an
  1157.  OS/2 kernel program. Although a call to WinInitialize is necessary in a
  1158.  Presentation Manager program, it isn't sufficient. You can call
  1159.  WinInitialize from an old-fashioned character-mode OS/2 program also.
  1160.  Getting that anchor block handle lets you access some functions within the
  1161.  Presentation Manager──the heap management and atom management functions──
  1162.  that are not directly connected with the windowing or graphics facilities of
  1163.  the Presentation Manager. But don't fret: Although we're not quite there
  1164.  yet, the next step will get us there.
  1165.  
  1166.  
  1167.  WEL ── Creating a Message Queue
  1168.  
  1169.  Calling WinInitialize to get an anchor block handle is like getting a pass
  1170.  to the pool. The next step──creating a message queue──is like jumping
  1171.  in. (We'll soon be swimming laps.) As you know, OS/2 supports multiple
  1172.  sessions, one being the Presentation Manager session. A program that creates
  1173.  a message queue is always run in the Presentation Manager session along with
  1174.  other Presentation Manager programs. The WEL program in Figure 2-3 shows
  1175.  how to create this message queue.
  1176.  
  1177.  Figure 2-3.  The WEL program.
  1178.  
  1179.    The WEL File
  1180.  
  1181.    #---------------
  1182.    # WEL make file
  1183.    #---------------
  1184.  
  1185.    wel.obj : wel.c
  1186.         cl -c -G2s -W3 wel.c
  1187.  
  1188.    wel.exe : wel.obj wel.def
  1189.         link wel, /align:16, NUL, os2, wel
  1190.  
  1191.    The WEL.C File
  1192.  
  1193.    /*-------------------------------------------------
  1194.       WEL.C -- A Program that Creates a Message Queue
  1195.      -------------------------------------------------*/
  1196.  
  1197.    #include <os2.h>
  1198.  
  1199.    int main (void)
  1200.         {
  1201.         HAB  hab ;
  1202.         HMQ  hmq ;
  1203.  
  1204.         hab = WinInitialize (0) ;
  1205.         hmq = WinCreateMsgQueue (hab, 0) ;
  1206.  
  1207.         WinDestroyMsgQueue (hmq) ;
  1208.         WinTerminate (hab) ;
  1209.         return 0 ;
  1210.         }
  1211.  
  1212.    The WEL.DEF File
  1213.  
  1214.    ;--------------------------------
  1215.    ; WEL.DEF module definition file
  1216.    ;--------------------------------
  1217.  
  1218.    NAME           WEL       WINDOWAPI
  1219.  
  1220.    DESCRIPTION    'Welcome to PM -- Program No. 3 (C) Charles Petzold, 1988'
  1221.    PROTMODE
  1222.    HEAPSIZE       1024
  1223.    STACKSIZE      2048
  1224.  
  1225.  
  1226.  The Message Queue Difference
  1227.  
  1228.  As you'll see, Presentation Manager programs are based on a message-driven
  1229.  input model. Programs receive all input in the form of messages. We're not
  1230.  quite ready to look at this message system in detail, but after working with
  1231.  it, you'll probably realize that this input model is almost a necessary part
  1232.  of a windowing environment like the Presentation Manager.
  1233.  
  1234.  Many messages that a program receives from the Presentation Manager are
  1235.  stored in a message queue. This message queue must be created explicitly by
  1236.  the program with a call to the Presentation Manager. This call establishes
  1237.  the program as a Presentation Manager application.
  1238.  
  1239.  Following the WinInitialize call, WEL.C makes this call:
  1240.  
  1241.    hmq = WinCreateMsgQueue (hab, 0) ;
  1242.  
  1243.  This call (as the name implies) creates a message queue. Like most
  1244.  Presentation Manager functions, WinCreateMsgQueue requires a handle as the
  1245.  first parameter. This is the anchor block handle, which is the only handle
  1246.  we have so far. The second parameter indicates the size of the queue, where
  1247.  0 means a default size sufficient for most programs. The value returned from
  1248.  the function is the handle to the message queue. This is stored in a
  1249.  variable named hmq of type HMQ. The program destroys the queue like this:
  1250.  
  1251.    WinDestroyMsgQueue (hmq) ;
  1252.  
  1253.  Following this call, the hmq handle is invalid.
  1254.  
  1255.  Message queues get a little more complex for programs with multiple threads
  1256.  of execution. A message queue is always associated with a particular thread
  1257.  thread──the thread that creates it. A thread can have only one message
  1258.  queue. In a multithread program, some threads can create message queues, but
  1259.  others don't have to.
  1260.  
  1261.  When OS/2 is booted, the first program that calls WinCreateMsgQueue
  1262.  (normally, the Presentation Manager shell) establishes a session as the
  1263.  Presentation Manager session. It is during the WinCreateMsgQueue call that
  1264.  the screen display is switched from character mode to graphics mode. Later
  1265.  programs that call WinCreateMsgQueue──even if executed from the CMD.EXE
  1266.  prompt in a character-mode session──are run in this same session.
  1267.  
  1268.  Notice also that the WINDOWCOMPAT keyword in W.DEF and WE.DEF has been
  1269.  changed to WINDOWAPI in WEL.DEF. This causes LINK to set a flag in the
  1270.  WEL.EXE file to inform OS/2 that this is truly a Presentation Manager
  1271.  program.
  1272.  
  1273.  Inhibiting Stack Checks
  1274.  
  1275.  Yet another switch, -Gs, has been added to the compile step. This switch is
  1276.  combined with the -G2 switch and written as -G2s.
  1277.  
  1278.  Normally, the C compiler inserts a call to the _chkstk function in the
  1279.  prologue section of every function in your program. This _chkstk function
  1280.  determines if the amount of space necessary for local variables in the
  1281.  function will cause a stack overflow. If so, the function displays a message
  1282.  to the standard error output device (the screen) using DosWrite and
  1283.  terminates the program. In the Presentation Manager, however, this approach
  1284.  is ineffective, because the Presentation Manager ignores output written to
  1285.  the display through DosWrite. The -Gs switch removes the checks for stack
  1286.  overflow. You should instead be sure that the stack size specified in the
  1287.  module definition file is sufficient for the program's needs.
  1288.  
  1289.  
  1290.  WELC ── Creating a Standard Window
  1291.  
  1292.  A program running in the Presentation Manager session occupies one or more
  1293.  windows. In simple terms, a window is a rectangular area of the screen that
  1294.  the program uses to receive input and display its output. A window is like a
  1295.  virtual terminal. A user can move and resize the windows on the screen and
  1296.  select one window (and hence one program) as the active, or foreground,
  1297.  window. A Presentation Manager program must create the window that the
  1298.  program uses. The WELC program in Figure 2-4 shows how this is done.
  1299.  
  1300.  Figure 2-4.  The WELC program.
  1301.  
  1302.    The WELC File
  1303.  
  1304.    #----------------
  1305.    # WELC make file
  1306.    #----------------
  1307.  
  1308.    welc.obj : welc.c
  1309.         cl -c -G2s -W3 welc.c
  1310.  
  1311.    welc.exe : welc.obj welc.def
  1312.         link welc, /align:16, NUL, os2, welc
  1313.  
  1314.    The WELC.C File
  1315.  
  1316.    /*----------------------------------------------------------
  1317.       WELC.C -- A Program that Creates a Standard Frame Window
  1318.      ----------------------------------------------------------*/
  1319.  
  1320.    #include <os2.h>
  1321.  
  1322.    int main (void)
  1323.         {
  1324.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  1325.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  1326.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  1327.  
  1328.         HAB          hab ;
  1329.         HMQ          hmq ;
  1330.         HWND         hwndFrame ;
  1331.  
  1332.         hab = WinInitialize (0) ;
  1333.         hmq = WinCreateMsgQueue (hab, 0) ;
  1334.  
  1335.         hwndFrame = WinCreateStdWindow (
  1336.                        HWND_DESKTOP,       // Parent window handle
  1337.                        WS_VISIBLE,         // Style of frame window
  1338.                        &flFrameFlags,      // Pointer to control data
  1339.                        NULL,               // Client window class name
  1340.                        NULL,               // Title bar text
  1341.                        0L,                 // Style of client window
  1342.                        NULL,               // Module handle for resources
  1343.                        0,                  // ID of resources
  1344.                        NULL) ;             // Pointer to client window handle
  1345.  
  1346.         WinDestroyWindow (hwndFrame) ;
  1347.         WinDestroyMsgQueue (hmq) ;
  1348.         WinTerminate (hab) ;
  1349.         return 0 ;
  1350.         }
  1351.  
  1352.    The WELC.DEF File
  1353.  
  1354.    ;---------------------------------
  1355.    ; WELC.DEF module definition file
  1356.    ;---------------------------------
  1357.  
  1358.    NAME           WELC      WINDOWAPI
  1359.  
  1360.    DESCRIPTION    'Welcome to PM -- Program No. 4 (C) Charles Petzold, 1988'
  1361.    PROTMODE
  1362.    HEAPSIZE       1024
  1363.    STACKSIZE      8192
  1364.  
  1365.  
  1366.  WELC.EXE is the first version of the program that has a substantial, visible
  1367.  result. When you run WELC, a window appears that looks much like the windows
  1368.  of other programs running under the Presentation Manager. The window
  1369.  contains a thick resizing border, a system menu box in the upper-left
  1370.  corner, a minimize and maximize box in the upper-right corner, and a title
  1371.  bar across the top containing the name of the program, WELC.EXE. Not bad──
  1372.  but not perfect. The problem is that this window disappears almost
  1373.  immediately after it's created. We'll fix that problem in the next version
  1374.  of our program, but first, let's examine what we've done to get this far.
  1375.  
  1376.  The WinCreateStdWindow Function
  1377.  
  1378.  WELC.C calls two Presentation Manager functions in addition to those
  1379.  introduced earlier: WinCreateStdWindow creates a window, and
  1380.  WinDestroyWindow destroys it. WinCreateStdWindow is the function normally
  1381.  used to create a main window for a Presentation Manager application. This
  1382.  isn't the only way to create an application window, but it's certainly the
  1383.  easiest. The WinCreateStdWindow function requires nine parameters, which are
  1384.  identified with comments in WELC.C. (The double slashes are recognized by
  1385.  the Microsoft C Compiler as setting off single-line comments.) Six of the
  1386.  parameters are set to 0 or NULL in this example. Certainly, we're not yet
  1387.  taking advantage of WinCreateStdWindow's full potential.
  1388.  
  1389.  WinCreateStdWindow creates a type of window known as a "frame window." We'll
  1390.  examine what this means a little later. The function returns a handle to the
  1391.  frame window. In WELC.C this handle is stored in a variable named hwndFrame
  1392.  and defined as type HWND ("handle to a window"). This handle must be used in
  1393.  other Presentation Manager functions to refer to the window. For example, in
  1394.  WELC.C this window handle is passed to WinDestroyWindow to destroy the
  1395.  window, which means that the Presentation Manager frees all the resources
  1396.  associated with the window and removes it from the screen. The window handle
  1397.  then becomes invalid.
  1398.  
  1399.  The first parameter to WinCreateStdWindow is the identifier known as
  1400.  HWND_DESKTOP (defined in PMWIN.H as 1), which specifies the "parent" of the
  1401.  frame window. This concept will be explored in more detail in the next
  1402.  chapter.
  1403.  
  1404.  The second parameter specifies the style of the window. The parameter is the
  1405.  identifier WS_VISIBLE (which is defined in PMWIN.H as the value
  1406.  0x80000000L). The WS prefix stands for "window style." This value instructs
  1407.  the WinCreateStdWindow function to make the window visible when it is
  1408.  created.
  1409.  
  1410.  The third parameter is a pointer to the variable flFrameFlags. The "fl"
  1411.  prefix is an example of "Hungarian notation," which I alluded to in Chapter
  1412.  1. The "f" indicates that the variable is a series of flags, and the "l"
  1413.  indicates that the flags are encoded in a 32-bit long data type. The
  1414.  flFrameFlags variable is defined as a ULONG (unsigned long). This "control
  1415.  data" parameter tells WinCreateStdWindow what the standard window should
  1416.  include. I've initialized flFrameFlags like this:
  1417.  
  1418.    static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  1419.                                FCF_SIZEBORDER    | FCF_MINMAX  |
  1420.                                FCF_SHELLPOSITION | FCF_TASKLIST ;
  1421.  
  1422.  The FCF ("frame creation flags") identifiers are defined in PMWIN.H. Some of
  1423.  these identifiers are almost self-explanatory: FCF_TITLEBAR means that we
  1424.  want a title bar across the top of the window; FCF_SYSMENU, a system menu
  1425.  box to the left of the title bar; FCF_SIZEBORDER, a thick sizing border
  1426.  around the window; and FCF_MINMAX, a minimize and maximize box to the right
  1427.  of the title bar. FCF_SHELLPOSITION instructs the Presentation Manager shell
  1428.  to give the window a default size and position on the screen. FCF_TASKLIST
  1429.  installs the program on the Task Manager.
  1430.  
  1431.  Here's how these six identifiers are defined in PMWIN.H:
  1432.  
  1433.    #define FCF_TITLEBAR      0x00000001L
  1434.    #define FCF_SYSMENU       0x00000002L
  1435.    #define FCF_SIZEBORDER    0x00000008L
  1436.    #define FCF_MINMAX        0x00000030L
  1437.    #define FCF_SHELLPOSITION 0x00000400L
  1438.    #define FCF_TASKLIST      0x00000800L
  1439.  
  1440.  Each identifier is a 32-bit constant with one or two bits set to 1 and the
  1441.  other bits set to 0. These identifiers are combined into one 32-bit number
  1442.  using the C bitwise OR operator (|). Many identifiers defined in the header
  1443.  files work this way.
  1444.  
  1445.  A Larger Stack
  1446.  
  1447.  You'll notice that the WELC.DEF file specifies a STACKSIZE value of 8192.
  1448.  The earlier programs have a 2 KB stack. The 8 KB stack is required for any
  1449.  program that creates a window, even if the window is displayed only
  1450.  momentarily.
  1451.  
  1452.  Of course, most windows created by Presentation Manager programs remain on
  1453.  the screen longer than the window in WELC. Our first priority is to fix that
  1454.  problem.
  1455.  
  1456.  
  1457.  WELCO ── Looping Through the Messages
  1458.  
  1459.  The problem with WELC is that we don't have a chance to enjoy the wonderful
  1460.  window we've created. The program calls WinCreateStdWindow to create the
  1461.  frame window but then calls WinDestroyWindow to blow it away. Obviously, we
  1462.  have to insert some code between those two function calls to keep the window
  1463.  up on the screen a little longer. If this were a conventional OS/2 program,
  1464.  you might set up a little loop to call KbdCharIn and then wait for a
  1465.  keystroke before destroying the window. But the KbdCharIn function isn't
  1466.  allowed in Presentation Manager programs. Nor are any of the other keyboard
  1467.  functions provided by the OS/2 kernel. What we can do instead is add a
  1468.  "message loop." This message loop is something like a loop that reads the
  1469.  keyboard, but it is much, much more. A program with a message loop──WELCO──
  1470.  is shown in Figure 2-5.
  1471.  
  1472.  Figure 2-5.  The WELCO program.
  1473.  
  1474.    The WELCO File
  1475.  
  1476.    #-----------------
  1477.    # WELCO make file
  1478.    #-----------------
  1479.  
  1480.    welco.obj : welco.c
  1481.         cl -c -G2s -W3 welco.c
  1482.  
  1483.    welco.exe : welco.obj welco.def
  1484.         link welco, /align:16, NUL, os2, welco
  1485.  
  1486.    The WELCO.C File
  1487.  
  1488.    /*------------------------------------------
  1489.       WELCO.C -- A Program with a Message Loop
  1490.      ------------------------------------------*/
  1491.  
  1492.    #include <os2.h>
  1493.  
  1494.    int main (void)
  1495.         {
  1496.  
  1497.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  1498.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  1499.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  1500.         HAB          hab ;
  1501.         HMQ          hmq ;
  1502.         HWND         hwndFrame ;
  1503.         QMSG         qmsg ;
  1504.  
  1505.         hab = WinInitialize (0) ;
  1506.         hmq = WinCreateMsgQueue (hab, 0) ;
  1507.  
  1508.         hwndFrame = WinCreateStdWindow (
  1509.                        HWND_DESKTOP,       // Parent window handle
  1510.                        WS_VISIBLE,         // Style of frame window
  1511.                        &flFrameFlags,      // Pointer to control data
  1512.                        NULL,               // Client window class name
  1513.                        NULL,               // Title bar text
  1514.                        0L,                 // Style of client window
  1515.                        NULL,               // Module handle for resources
  1516.                        0,                  // ID of resources
  1517.                        NULL) ;             // Pointer to client window handle
  1518.  
  1519.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  1520.              WinDispatchMsg (hab, &qmsg) ;
  1521.  
  1522.         WinDestroyWindow (hwndFrame) ;
  1523.         WinDestroyMsgQueue (hmq) ;
  1524.         WinTerminate (hab) ;
  1525.         return 0 ;
  1526.         }
  1527.  
  1528.    The WELCO.DEF File
  1529.  
  1530.    ;----------------------------------
  1531.    ; WELCO.DEF module definition file
  1532.    ;----------------------------------
  1533.  
  1534.    NAME           WELCO     WINDOWAPI
  1535.  
  1536.    DESCRIPTION    'Welcome to PM -- Program No. 5 (C) Charles Petzold, 1988'
  1537.    PROTMODE
  1538.    HEAPSIZE       1024
  1539.    STACKSIZE      8192
  1540.  
  1541.  
  1542.  When you run WELCO.EXE under the Presentation Manager, you'll be treated to
  1543.  a real Presentation Manager window, as shown in Figure 2-6. With this
  1544.  window you can
  1545.  
  1546.    ■  Press the mouse button when the pointer is positioned over the title
  1547.       bar and drag the window around the screen.
  1548.  
  1549.    ■  Drag the sizing border to change the size of the window.
  1550.  
  1551.    ■  Click on the maximize arrow and expand the window to full screen.
  1552.  
  1553.    ■  Click on the minimize arrow and compress the window into a little
  1554.       square.
  1555.  
  1556.    ■  Use the mouse or keyboard to invoke the system menu.
  1557.  
  1558.    ■  Size or move the window with the keyboard.
  1559.  
  1560.    ■  Use Alt with a function key to invoke system menu options.
  1561.  
  1562.    ■  Close the window, removing it from the screen.
  1563.  
  1564.  That's a considerable improvement, considering that only three lines were
  1565.  added to the program.
  1566.  
  1567.  Anatomy of a Window
  1568.  
  1569.  As we develop a Presentation Manager program in this chapter, we will
  1570.  encounter three major concepts that are central to Presentation Manager
  1571.  programming:
  1572.  
  1573.    ■  Windows
  1574.    ■  Messages
  1575.    ■  Presentation spaces
  1576.  
  1577.  These three concepts are closely related: A window receives input in the
  1578.  form of messages and displays output to a presentation space. This entire
  1579.  book is about receiving messages and writing to presentation spaces. The
  1580.  window is at the center of it all.
  1581.  
  1582.  Earlier I said that a window is a rectangular area on the screen. That's too
  1583.  easy. Sure, a window occupies an area on the screen, but that's what the
  1584.  window looks like, not what it is. As you start programming for the
  1585.  Presentation Manager, windows seem to take on life. You will use
  1586.  anthropomorphic language when thinking and talking about windows. You will
  1587.  say a window does something, a window responds in a certain way, and a
  1588.  window has a style. A window has a parent and can also have children; a
  1589.  window can talk to another window. And yes, a window occupies a rectangular
  1590.  area on the screen.
  1591.  
  1592.  You'll find it helpful to think of windows in terms common in
  1593.  object-oriented programming. For example, you might now believe that some
  1594.  code someplace in the Presentation Manager draws the sizing border, system
  1595.  menu box, title bar, and minimize/maximize box so that they look the way
  1596.  they do. Yes, but no──you're closer to reality if you think of the window
  1597.  as drawing itself. The window itself determines how it will look.
  1598.  
  1599.  This may become clearer if I discuss the WinCreateStdWindow function more.
  1600.  I've been speaking about the window that WinCreateStdWindow creates as if it
  1601.  were a single window. Actually, WinCreateStdWindow is a high-level function
  1602.  that does the work of several other functions. As used in WELC,
  1603.  WinCreateStdWindow causes four windows to be created:
  1604.  
  1605.    ■  A frame window
  1606.    ■  A title bar window
  1607.    ■  A system menu window
  1608.    ■  A minimize/maximize window
  1609.  
  1610.  (A fifth window──the drop-down menu displayed from the system menu──is
  1611.  also created. But let's ignore that for this discussion.)
  1612.  
  1613.  These are separate windows. They are certainly bound together into one tidy
  1614.  unit, and they certainly have some relationship among themselves, but in
  1615.  other ways these windows are distinct and independent.
  1616.  
  1617.  The WinCreateStdWindow function creates the frame window, and the frame
  1618.  window creates the other three windows. These three windows correspond to
  1619.  the FCF_TITLEBAR, FCF_SYSMENU, and FCF_MINMAX flags set in the flFrameFlags
  1620.  parameter that is passed to WinCreateStdWindow. Each of these four windows
  1621.  has its own window handle. WinCreateStdWindow returns only the window handle
  1622.  of the frame window, but the other handles are available if you need them.
  1623.  
  1624.  The frame window is like a base on which the other three windows are
  1625.  arranged. Each of these four windows draws itself. The frame window draws
  1626.  itself as a solid background surrounded by a sizing border. The title bar
  1627.  window, system menu window, and minimize/maximize window are relatively
  1628.  small windows that sit on top of the frame window.
  1629.  
  1630.  Each of these four windows is distinct in appearance because each window
  1631.  draws itself in a unique way. Each window responds to input in a distinct
  1632.  way because each window processes its own input. This input takes the form
  1633.  of "messages."
  1634.  
  1635.  Messages
  1636.  
  1637.  In a conventional operating system, you must always ask for information. In
  1638.  the Presentation Manager, information is delivered to your program in the
  1639.  form of "messages." For example, in a conventional OS/2 kernel program, you
  1640.  can determine the size of the screen display in units of characters or
  1641.  pixels by calling the VioGetMode function. In a Presentation Manager
  1642.  program, the size of the screen is less important than the size of one of
  1643.  your program's windows. The size of these windows can change. The window is
  1644.  notified of such a change through messages. Messages are notifications of
  1645.  user input and everything else that affects the program's windows.
  1646.  
  1647.  A Presentation Manager program works by processing messages. In fact, it
  1648.  does little else except process messages. We say that a Presentation Manager
  1649.  program is "message-driven."
  1650.  
  1651.  A message is a data structure of type QMSG (queue message), which is defined
  1652.  in PMWIN.H as shown below.
  1653.  
  1654.    typedef struct _QMSG
  1655.         {
  1656.         HWND   hwnd ;
  1657.         USHORT msg ;
  1658.         MPARAM mp1 ;
  1659.         MPARAM mp2 ;
  1660.         ULONG  time ;
  1661.         POINTL ptl ;
  1662.         }
  1663.         QMSG ;
  1664.  
  1665.  A message is usually directed to a particular window. The handle of the
  1666.  intended recipient of a message is given in the hwnd field of the structure.
  1667.  
  1668.  The msg field (defined as type USHORT, or unsigned short) identifies the
  1669.  message. All messages have identifiers defined in PMWIN.H. Many of them
  1670.  begin with the letters WM ("window message"). Examples of these identifiers
  1671.  are WM_CREATE, WM_SIZE, WM_CHAR, WM_MOUSEMOVE, WM_PAINT, WM_DESTROY, and
  1672.  WM_QUIT. The mp1 and mp2 fields (defined as type MPARAM, which is a 32-bit
  1673.  far pointer) are "message parameters." They contain information connected
  1674.  with the particular message; The time field is the time the message was
  1675.  sent, and ptl (a POINTL structure) indicates the position of the mouse
  1676.  pointer at the time the message was sent. The following table summarizes
  1677.  this information:
  1678.  
  1679.                      THE MESSAGE STRUCTURE
  1680.     The message is addressed to               hwnd
  1681.     The message is                            msg
  1682.     More detailed information is found in     mp1 and mp2
  1683.     The time of the message is                time
  1684.     The mouse pointer was positioned at       ptl
  1685.  
  1686.  When a message is addressed to a particular window (the usual case), the
  1687.  window processes the message. Everything a window does is the result of
  1688.  processing messages.
  1689.  
  1690.  The message queue is a place where messages are stored. After a thread
  1691.  creates a message queue by calling WinCreateMsgQueue, the Presentation
  1692.  Manager uses this queue to store messages to all windows created in that
  1693.  thread. Not all messages are stored in the message queue (a distinction I'll
  1694.  discuss a little later), but most messages relating directly to user input
  1695.  are stored there. The message queue created by WELCO stores messages for the
  1696.  frame window, the title bar window, the system menu window, and the
  1697.  minimize/maximize window.
  1698.  
  1699.  The Message Loop
  1700.  
  1701.  After a thread creates a message queue, it can create windows. Messages for
  1702.  the windows created in the thread are stored in the thread's message queue.
  1703.  Messages are retrieved from the message queue in a two-line piece of code
  1704.  called the "message loop." The program first must define a variable of type
  1705.  QMSG, the message structure:
  1706.  
  1707.    QMSG  qmsg ;
  1708.  
  1709.  After creating its windows, the program enters the message loop:
  1710.  
  1711.    while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  1712.         WinDispatchMsg (hab, &qmsg) ;
  1713.  
  1714.  Note that the last three parameters in the WinGetMsg call are set to NULL or
  1715.  0. This is normal: It indicates that WinGetMsg should retrieve all messages
  1716.  to all windows created in that thread.
  1717.  
  1718.  WinGetMsg passes to the Presentation Manager a pointer to the QMSG message
  1719.  structure. The Presentation Manager fills the fields of the structure with
  1720.  the next message from the queue and returns control to the program. When
  1721.  WinGetMsg returns, the QMSG structure holds a valid message from the message
  1722.  queue. The program then "dispatches" the message by calling WinDispatchMsg.
  1723.  When WinDispatchMsg returns, the program again calls WinGetMsg. If there are
  1724.  no messages in the queue, WinGetMsg waits until one is available. For all
  1725.  messages except WM_QUIT, WinGetMsg returns a nonzero value. WM_QUIT is a
  1726.  very special message. It causes WinGetMsg to return a 0 value and fall out
  1727.  of the while loop. (The WM_QUIT message is put into the queue when you
  1728.  select Close from the system menu.) The program then makes calls to
  1729.  WinDestroyWindow, WinDestroyMsgQueue, and WinTerminate and exits main,
  1730.  ending the program.
  1731.  
  1732.  Do you find this message loop code a little peculiar? The program fetches a
  1733.  message from the queue with WinGetMsg. That's OK. But the program is
  1734.  seemingly not doing anything with the message. It's simply throwing the
  1735.  message away by calling WinDispatchMsg. If the message is actually being
  1736.  dispatched somewhere, who's getting it? Where does the message go? Well, the
  1737.  message is addressed to a particular window, so obviously that window gets
  1738.  the message. WinDispatchMsg sends a message to a window.
  1739.  
  1740.  Perhaps this is still bothering you. Perhaps you're not quite comfortable
  1741.  with the concept of a window getting messages──it's too abstract. Would it
  1742.  make more sense if I said that WinDispatchMsg causes a function to be
  1743.  called? And that the message being dispatched takes the form of parameters
  1744.  to the function? And that this function interprets these parameters and does
  1745.  something to process the message? Would you be more comfortable with the
  1746.  idea that this function──in a very real sense──is the window?
  1747.  
  1748.  The Window Procedure
  1749.  
  1750.  Every window has an associated window procedure, which processes messages
  1751.  for the window. The window procedure determines how the window responds to
  1752.  input (in the form of messages) and what the window looks like.
  1753.  
  1754.  WinGetMsg retrieves messages addressed to all windows that have been created
  1755.  in the thread of the process. During the WinDispatchMsg call, the
  1756.  Presentation Manager determines the address of the window procedure for the
  1757.  window whose handle is in the hwnd field of the message structure. It then
  1758.  calls this window procedure. The window procedure processes the message and
  1759.  returns control to the Presentation Manager, which then returns control to
  1760.  the program that called WinDispatchMsg.
  1761.  
  1762.  The window procedures for the four windows created in WELCO are located in
  1763.  PMWIN.DLL, one of the dynamic link library modules that constitute the
  1764.  Presentation Manager. For example, PMWIN.DLL contains a function called
  1765.  WinTitlebarWndProc. This function is the window procedure that processes
  1766.  messages for all title bars created by all programs currently running under
  1767.  the Presentation Manager. The title bar window displays text because that
  1768.  happens to be the way the window procedure draws the window. The title bar
  1769.  changes color to indicate that the program (or more precisely, the frame
  1770.  window) is active because the frame window sends the title bar window a
  1771.  message telling it to change the color. The title bar window responds to
  1772.  mouse input in its own specialized way to allow the window to be
  1773.  repositioned on the screen, and it then sends a message to the frame window
  1774.  informing it of the new position.
  1775.  
  1776.  A typical window procedure is shown in Figure 2-7. Note that the four
  1777.  parameters to the window procedure are the first four fields of the message
  1778.  structure──the window handle, the message identifier, and the two MPARAM
  1779.  values that provide message-specific information. When the WinDispatchMsg
  1780.  function calls the window procedure, it extracts these four fields from the
  1781.  structure to pass to the window procedure.
  1782.  
  1783.  A window procedure generally processes messages using a switch and case
  1784.  construction. For each type of message, the mp1 and mp2 parameters provide
  1785.  additional information about the message.
  1786.  
  1787.  Figure 2-7.  A typical window procedure.
  1788.  
  1789.    MRESULT EXPENTRY DoodadWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  1790.         {
  1791.              [definitions of local variables]
  1792.         switch (msg)
  1793.              {
  1794.              case WM_CREATE:
  1795.                        [do initialization]
  1796.                   return 0 ;
  1797.              case WM_PAINT:
  1798.                        [paint the window]
  1799.                   return 0 ;
  1800.              case WM_CHAR:
  1801.                        [process keyboard messages]
  1802.                   return 0 ;
  1803.              case WM_MOUSEMOVE:
  1804.                        [process mouse movement messages]
  1805.                   return 0 ;
  1806.              case WM_DESTROY:
  1807.                        [clean up]
  1808.                   return 0 ;
  1809.              }
  1810.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  1811.         }
  1812.  
  1813.  
  1814.  The value the window procedure returns depends on the message. Usually it's
  1815.  a 0. Any message the window doesn't process must be passed on to a function
  1816.  called WinDefWindowProc. This function does default processing of all
  1817.  messages that a window procedure chooses to ignore.
  1818.  
  1819.  Why are we spending time looking at the structure of window procedures that
  1820.  are internal to the Presentation Manager? Because not all window procedures
  1821.  are inside the Presentation Manager. Presentation Manager programs can also
  1822.  contain window procedures. In fact, they almost always do. And that's why we
  1823.  will soon add a window procedure──and a new window──to our program.
  1824.  
  1825.  
  1826.  WELCOM ── Adding a Standard Icon
  1827.  
  1828.  Before we add a new window to the program, let's address a little problem in
  1829.  WELCO.
  1830.  
  1831.  If you minimize WELCO, you'll see the program displayed at the bottom of
  1832.  the screen as a small nondescript white rectangle. It should look more like
  1833.  a normal Presentation Manager icon.
  1834.  
  1835.  We can add a standard application icon to the program with a few lines of
  1836.  code. The new version, called WELCOM, is shown in Figure 2-8.
  1837.  
  1838.  Figure 2-8.  The WELCOM program.
  1839.  
  1840.    The WELCOM File
  1841.  
  1842.    #------------------
  1843.    # WELCOM make file
  1844.    #------------------
  1845.  
  1846.    welcom.obj : welcom.c
  1847.         cl -c -G2sw -W3 welcom.c
  1848.  
  1849.    welcom.exe : welcom.obj welcom.def
  1850.         link welcom, /align:16, NUL, os2, welcom
  1851.  
  1852.    The WELCOM.C File
  1853.  
  1854.    /*------------------------------------------------
  1855.       WELCOM.C -- A Program that has a Standard Icon
  1856.      ------------------------------------------------*/
  1857.  
  1858.    #define INCL_WIN
  1859.    #include <os2.h>
  1860.  
  1861.    int main (void)
  1862.         {
  1863.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  1864.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  1865.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  1866.         HAB          hab ;
  1867.         HMQ          hmq ;
  1868.         HWND         hwndFrame ;
  1869.         QMSG         qmsg ;
  1870.  
  1871.         hab = WinInitialize (0) ;
  1872.         hmq = WinCreateMsgQueue (hab, 0) ;
  1873.  
  1874.  
  1875.         hwndFrame = WinCreateStdWindow (
  1876.                        HWND_DESKTOP,       // Parent window handle
  1877.                        WS_VISIBLE,         // Style of frame window
  1878.                        &flFrameFlags,      // Pointer to control data
  1879.                        NULL,               // Client window class name
  1880.                        NULL,               // Title bar text
  1881.                        0L,                 // Style of client window
  1882.                        NULL,               // Module handle for resources
  1883.                        0,                  // ID of resources
  1884.                        NULL) ;             // Pointer to client window handle
  1885.  
  1886.         WinSendMsg (hwndFrame, WM_SETICON,
  1887.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  1888.                     NULL) ;
  1889.  
  1890.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  1891.              WinDispatchMsg (hab, &qmsg) ;
  1892.  
  1893.         WinDestroyWindow (hwndFrame) ;
  1894.         WinDestroyMsgQueue (hmq) ;
  1895.         WinTerminate (hab) ;
  1896.         return 0 ;
  1897.         }
  1898.  
  1899.    The WELCOM.DEF File
  1900.  
  1901.    ;-----------------------------------
  1902.    ; WELCOM.DEF module definition file
  1903.    ;-----------------------------------
  1904.  
  1905.    NAME           WELCOM    WINDOWAPI
  1906.  
  1907.    DESCRIPTION    'Welcome to PM -- Program No. 6 (C) Charles Petzold, 1988'
  1908.    PROTMODE
  1909.    HEAPSIZE       1024
  1910.    STACKSIZE      8192
  1911.  
  1912.  
  1913.  Many Presentation Manager programs use customized icons to identify the
  1914.  program when it is minimized and displayed at the bottom of the screen.
  1915.  We'll begin doing this in Chapter 12. Until then, we'll use a standard icon
  1916.  that is defined within the Presentation Manager.
  1917.  
  1918.  The frame window must display an icon when the window is minimized. We have
  1919.  to tell the frame window what icon to use for this. I've been discussing
  1920.  messages, and here we can see an example of one. A program can tell the
  1921.  frame window which icon to use by sending the frame window a WM_SETICON
  1922.  message. You do this by calling the WinSendMsg function:
  1923.  
  1924.    WinSendMsg (hwndFrame, WM_SETICON,
  1925.                WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  1926.                NULL) ;
  1927.  
  1928.  You'll recall that a window procedure has four parameters: the window
  1929.  handle, the message identifier, and two message parameters. WinSendMsg has
  1930.  these same four parameters. The first is hwndFrame, the handle of the window
  1931.  to which we're sending the message. The second parameter, WM_SETICON,
  1932.  identifies the message. The last two parameters to WinSendMsg correspond to
  1933.  the mp1 and mp2 parameters of the window procedure. These provide
  1934.  information unique to the WM_SETICON message.
  1935.  
  1936.  For WM_SETICON, mp2 is not used and can be set to NULL. The mp1 parameter is
  1937.  a handle to a pointer. (The word "pointer," as used here, generally refers
  1938.  to the mouse pointer that you move on the screen using your mouse. But as
  1939.  you'll discover in Chapter 12, icons and mouse pointers are closely related
  1940.  and interchangeable in many cases.) This handle is obtained from the
  1941.  WinQuerySysPointer function call. The first parameter of this handle is
  1942.  HWND_DESKTOP, which is required for this function. The SPTR_APPICON
  1943.  identifier (the SPTR prefix stands for "system pointer") refers to a simple
  1944.  icon that looks like a little window if you use your imagination.
  1945.  
  1946.  The last parameter to WinQuerySysPointer is set to FALSE to indicate that we
  1947.  do not want the Presentation Manager to make a copy of this icon. All we
  1948.  want is the handle to it. This is one case where you don't call a function
  1949.  to destroy the resources connected with the handle. You don't need to save
  1950.  the handle returned from WinQuerySysPointer; you just pass it to the
  1951.  WinSendMsg function.
  1952.  
  1953.  You'll notice that I've added the following line to the top of WELCOM.C:
  1954.  
  1955.    #define INCL_WIN
  1956.  
  1957.  This line appears before the #include statement for OS2.H. The declaration
  1958.  for the WinQuerySysPointer function and the definition of the SPTR_APPICON
  1959.  identifier in PMWIN.H (as well as a number of other functions and
  1960.  identifiers) are not included by default. Defining INCL_WIN causes them to
  1961.  be included.
  1962.  
  1963.  
  1964.  WELCOME ── Creating a Client Window
  1965.  
  1966.  The four windows in WELCOM seem to get along OK. But it's like a party
  1967.  taking place in your house to which you weren't invited. After WELCOM
  1968.  creates the four windows, all it does is retrieve messages from the message
  1969.  queue and dispatch them to window procedures located somewhere in PMWIN.DLL.
  1970.  Let's get in on this action. In the WELCOME version of our program, shown in
  1971.  Figure 2-9, I've changed the WinCreateStdWindow call slightly so that it
  1972.  creates a fifth window. This window will fill that large area between the
  1973.  title bar and the visible parts of the sizing border, covering the
  1974.  still-visible part of the frame window. This fifth window is our window──
  1975.  we process the messages to it.
  1976.  
  1977.  Figure 2-9.  The WELCOME program.
  1978.  
  1979.    The WELCOME File
  1980.  
  1981.    #-------------------
  1982.    # WELCOME make file
  1983.    #-------------------
  1984.  
  1985.    welcome.obj : welcome.c
  1986.         cl -c -G2sw -W3 welcome.c
  1987.  
  1988.    welcome.exe : welcome.obj welcome.def
  1989.         link welcome, /align:16, NUL, os2, welcome
  1990.  
  1991.    The WELCOME.C File
  1992.  
  1993.    /*-----------------------------------------------------
  1994.       WELCOME.C -- A Program that Creates a Client Window
  1995.      -----------------------------------------------------*/
  1996.  
  1997.    #define INCL_WIN
  1998.    #include <os2.h>
  1999.  
  2000.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  2001.  
  2002.    int main (void)
  2003.         {
  2004.         static CHAR  szClientClass [] = "Welcome" ;
  2005.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  2006.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  2007.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  2008.         HAB          hab ;
  2009.         HMQ          hmq ;
  2010.         HWND         hwndFrame, hwndClient ;
  2011.         QMSG         qmsg ;
  2012.  
  2013.         hab = WinInitialize (0) ;
  2014.         hmq = WinCreateMsgQueue (hab, 0) ;
  2015.  
  2016.         WinRegisterClass (
  2017.                        hab,                // Anchor block handle
  2018.                        szClientClass,      // Name of class being registered
  2019.                        ClientWndProc,      // Window procedure for class
  2020.                        0L,                 // Class style
  2021.                        0) ;                // Extra bytes to reserve
  2022.  
  2023.         hwndFrame = WinCreateStdWindow (
  2024.                        HWND_DESKTOP,       // Parent window handle
  2025.                        WS_VISIBLE,         // Style of frame window
  2026.                        &flFrameFlags,      // Pointer to control data
  2027.                        szClientClass,      // Client window class name
  2028.                        NULL,               // Title bar text
  2029.                        0L,                 // Style of client window
  2030.                        NULL,               // Module handle for resources
  2031.                        0,                  // ID of resources
  2032.                        &hwndClient) ;      // Pointer to client window handle
  2033.  
  2034.         WinSendMsg (hwndFrame, WM_SETICON,
  2035.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  2036.                     NULL) ;
  2037.  
  2038.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  2039.              WinDispatchMsg (hab, &qmsg) ;
  2040.  
  2041.         WinDestroyWindow (hwndFrame) ;
  2042.         WinDestroyMsgQueue (hmq) ;
  2043.         WinTerminate (hab) ;
  2044.         return 0 ;
  2045.         }
  2046.  
  2047.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  2048.         {
  2049.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  2050.         }
  2051.  
  2052.    The WELCOME.DEF File
  2053.  
  2054.    ;------------------------------------
  2055.    ; WELCOME.DEF module definition file
  2056.    ;------------------------------------
  2057.  
  2058.    NAME           WELCOME   WINDOWAPI
  2059.  
  2060.    DESCRIPTION    'Welcome to PM -- Program No. 7 (C) Charles Petzold, 1988'
  2061.    PROTMODE
  2062.    HEAPSIZE       1024
  2063.    STACKSIZE      8192
  2064.    EXPORTS        ClientWndProc
  2065.  
  2066.  
  2067.  This new window is called a "client window." Messages for this client window
  2068.  are stored in the message queue just like messages for the other four
  2069.  windows created in WinCreateStdWindow. The messages are retrieved from the
  2070.  queue with WinGetMsg and dispatched to the appropriate window procedure with
  2071.  WinDispatchMsg. But the window procedure for the client window is not in
  2072.  PMWIN.DLL. This window procedure is located in WELCOME itself.
  2073.  
  2074.  Registering a Window Class
  2075.  
  2076.  Every window has an associated window procedure. More precisely, every
  2077.  window is based on a particular "window class." It's the window class that
  2078.  defines the window procedure used to process messages for all windows
  2079.  created based on that class.
  2080.  
  2081.  The Presentation Manager has nine predefined window classes. (One of them,
  2082.  for example, is the class called WC_TITLEBAR, using the PMWIN.H identifier.)
  2083.  Each of these window classes has a window procedure located in PMWIN.DLL.
  2084.  (The window procedure for the WC_TITLEBAR class is WinTitlebarWndProc.) When
  2085.  WinCreateStdWindow was called in previous versions of the program, it
  2086.  created four windows based on four of these predefined window classes.
  2087.  Messages to these windows go to the window procedure for the window class.
  2088.  
  2089.  If you want WinCreateStdWindow to create a client window with a window
  2090.  procedure in your own program, you must first register a new window class
  2091.  that identifies this window procedure. You do this by calling
  2092.  WinRegisterClass, as shown in the WELCOME.C program. The second and third
  2093.  parameters to WinRegisterClass are the most important: They specify the name
  2094.  of the window class and the address of the window procedure for that class.
  2095.  The window procedure processes messages to all windows that are based on
  2096.  that class.
  2097.  
  2098.  The predefined window classes in PMWIN.DLL are "public" window classes: They
  2099.  can be used by all programs running under the Presentation Manager. When
  2100.  your program contains a window procedure and you register a class for it,
  2101.  that is a "private" class that can be used only by your program.
  2102.  
  2103.  The name of a private window class registered in a program is generally a
  2104.  character string that either is the name of the program or is derived from
  2105.  the name of the program, but it can really be anything you want. In
  2106.  WELCOME, the class name is the character string "Welcome" stored in the
  2107.  array szClientClass. (The sz prefix indicates that the variable is a string
  2108.  terminated by a zero byte.) The window procedure is the function named
  2109.  ClientWndProc, which in WELCOME is located after the main function. You can
  2110.  name the window procedure whatever you like. The window procedure is
  2111.  declared near the top of the program with the following statement:
  2112.  
  2113.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  2114.  
  2115.  Declaring ClientWndProc before main is mandatory: This lets the C compiler
  2116.  recognize ClientWndProc as a function when compiling code for the
  2117.  WinRegisterClass call.
  2118.  
  2119.  The EXPENTRY identifier stands for "exported entry point" and indicates that
  2120.  the function is called from outside the program. It is defined in OS2DEF.H
  2121.  in the same way as APIENTRY:
  2122.  
  2123.    #define EXPENTRY far pascal
  2124.  
  2125.  All window procedures must be defined as EXPENTRY functions.
  2126.  
  2127.  The New WinCreateStdWindow Call
  2128.  
  2129.  The next step is to change some of the parameters to WinCreateStdWindow so
  2130.  that it creates a client window in addition to the other five windows. The
  2131.  fourth parameter to WinCreateStdWindow (previously set to NULL) is now set
  2132.  to the name of the client window class, which is the character array
  2133.  szClientClass. The last parameter to WinCreateStdWindow is a pointer to a
  2134.  variable that will receive the handle of the client window when
  2135.  WinCreateStdWindow creates it. This variable is named hwndClient and defined
  2136.  as type HWND.
  2137.  
  2138.  WinCreateStdWindow now creates five windows, four of them based on
  2139.  predefined window classes and the fifth──the client window──based on the
  2140.  "Welcome" class. WinCreateStdWindow returns the window handle of the frame
  2141.  window, but it also stores the window handle of the client window in the
  2142.  variable pointed to by its last parameter.
  2143.  
  2144.  Processing the Messages
  2145.  
  2146.  The ClientWndProc window procedure in WELCOME is called only from the
  2147.  Presentation Manager, from outside the program's code segment, using the
  2148.  Pascal calling sequence, which is why it's defined as an EXPENTRY function.
  2149.  The window procedure returns an MRESULT (a 32-bit far pointer) to the
  2150.  Presentation Manager. ClientWndProc receives messages only for the client
  2151.  window. Whenever ClientWndProc is called, the hwnd parameter is the window
  2152.  handle of the client window. This is the same window handle stored in the
  2153.  hwndClient variable in main.
  2154.  
  2155.  ClientWndProc doesn't yet process any messages itself. Any message a window
  2156.  procedure doesn't process must be passed on to the WinDefWindowProc function
  2157.  in the Presentation Manager. The value returned from WinDefWindowProc is
  2158.  then returned from the window procedure.
  2159.  
  2160.  ───────────────────────────────────────────────────────────────────────────
  2161.  NOTE:
  2162.     That ClientWndProc doesn't process any messages causes a little problem:
  2163.     The client window isn't painted. If you experiment with WELCOME in the
  2164.     Presentation Manager, you'll find that the client window displays
  2165.     whatever was underneath it when it is created or resized! Of course,
  2166.     we'll fix this problem shortly.
  2167.  ───────────────────────────────────────────────────────────────────────────
  2168.  
  2169.  The Stream of Processing
  2170.  
  2171.  With the client window procedure in place, you can now get a good sense of
  2172.  how Presentation Manager programs are structured and how they operate. The
  2173.  main function first performs initialization. At the very least, this
  2174.  involves calls to WinInitialize, WinCreateMsgQueue, WinRegisterClass, and
  2175.  WinCreateStdWindow. It then enters the message loop. When it exits the
  2176.  message loop, it cleans up with WinDestroyWindow, WinDestroyMsgQueue, and
  2177.  WinTerminate and exits main, terminating the program.
  2178.  
  2179.  In the message loop, the program calls WinGetMsg, which retrieves the next
  2180.  message from the program's message queue. These messages include user input
  2181.  from the keyboard and mouse. The program passes the message back to the
  2182.  Presentation Manager by calling WinDispatchMsg. The Presentation Manager
  2183.  determines the address of the window procedure for the particular window
  2184.  that must receive the message, and it then calls the window procedure. This
  2185.  is either a predefined window procedure within the Presentation Manager or a
  2186.  window procedure within the program (such as ClientWndProc).
  2187.  
  2188.  The window procedure either processes the message or calls WinDefWindowProc.
  2189.  The window procedure then returns control to the Presentation Manager (still
  2190.  in the WinDispatchMsg call), which returns control to the program's message
  2191.  loop.
  2192.  
  2193.  This is a considerably more complex interaction between a program and an
  2194.  operating system than is typical in a more conventional operating system
  2195.  such as the OS/2 kernel. In the Presentation Manager, programs have a more
  2196.  intimate connection with the operating system and (potentially) other
  2197.  programs running under the Presentation Manager. It's the use of messages
  2198.  that makes the difference. Messages are the means of communication between
  2199.  the Presentation Manager and windows, and between windows themselves.
  2200.  
  2201.  Queued and Nonqueued Messages
  2202.  
  2203.  I've been discussing how messages get from the message queue to a window
  2204.  procedure. However, not all messages originate in the message queue. Window
  2205.  procedures can also be called directly from the Presentation Manager.
  2206.  
  2207.  When a message is placed in a program's message queue, retrieved with
  2208.  WinGetMsg, and dispatched to the window procedure with WinDispatchMsg, that
  2209.  message is said to be a "queued message." Many of the messages relating to
  2210.  user input (such as the WM_CHAR keyboard message and the WM_MOUSEMOVE mouse
  2211.  message) are "queued" messages. Timer messages are queued, as are menu
  2212.  messages (which signal a window procedure that a menu item has been chosen).
  2213.  But many other messages are sent to the window procedure directly without
  2214.  first being placed in the message queue. For example, the WM_CREATE message
  2215.  message──which is the first message that a window procedure receives──is
  2216.  sent to the window at the same time the Presentation Manager is executing
  2217.  the WinCreateStdWindow function. The WM_DESTROY message is sent to a window
  2218.  procedure as part of the Presentation Manager's processing of the
  2219.  WinDestroyWindow call. These are "nonqueued" messages.
  2220.  
  2221.  Whether a message is sent directly to a window procedure or dispatched to
  2222.  the window procedure after being retrieved from the message queue is
  2223.  generally not very important. The window procedure is "message central"──
  2224.  it gets all messages to the window. It usually doesn't matter what route the
  2225.  messages took to get to the window procedure.
  2226.  
  2227.  A window can also "post" or "send" messages to other windows. The WinPostMsg
  2228.  function places a message in the message queue associated with a particular
  2229.  window and returns immediately. The WinSendMsg function (which I used to
  2230.  send the frame window a WM_SETICON message) causes the Presentation Manager
  2231.  to call the window procedure directly. WinSendMsg returns after the window
  2232.  procedure has processed the message. (The WinDispatchMsg call used in the
  2233.  message loop is similar to the WinSendMsg call.)
  2234.  
  2235.  In short, post means to put the message in the mail box; send means to
  2236.  hand-deliver the message to the recipient. A message that is posted becomes
  2237.  a queued message; a message that is sent becomes a nonqueued message. As I
  2238.  said, from the perspective of the window procedure, the distinction is
  2239.  usually not very important. When speaking about messages, the term send is
  2240.  often used for convenience even when the message is actually posted. In the
  2241.  chapters ahead, I'll discuss whether a message is queued or nonqueued when
  2242.  necessary, but otherwise I'll tend to use this convenient terminology.
  2243.  
  2244.  Messages sometimes generate other messages. This can happen when a window
  2245.  procedure declines to process a message and passes the message to
  2246.  WinDefWindowProc. WinDefWindowProc sometimes does default processing of a
  2247.  message by sending the window procedure another message. Calling
  2248.  Presentation Manager functions also sometimes results in the window
  2249.  procedure being sent a message.
  2250.  
  2251.  This means that the window procedure must be recursive. Generally, this fact
  2252.  doesn't cause any problems, but you should keep it in the back of your mind.
  2253.  If you encounter a strange bug (a static local variable in your window
  2254.  procedure changing when you call a Presentation Manager function, for
  2255.  example), perhaps your window procedure is changing the variable itself
  2256.  while processing another message generated by the call to the Presentation
  2257.  Manager function. You should also keep at a reasonable level the size of
  2258.  local automatic variables in main and in the window procedures. The
  2259.  recursive use of window procedures is the primary reason for the minimum
  2260.  recommended 8 KB stack size in Presentation Manager programs. Feel free to
  2261.  increase it if you use large automatic variables in a window procedure.
  2262.  
  2263.  Special Treatment of Window Procedures
  2264.  
  2265.  Window procedures require some special attention when you compile and link
  2266.  the program. First, for any program that contains a window procedure, the
  2267.  compiler requires yet another switch: -Gw. (The w stands for "window.") This
  2268.  switch is combined with the -G2 and -Gs switches to make the -G2sw switch.
  2269.  Second, the window procedure must be mentioned in an EXPORTS statement in
  2270.  the module definition file:
  2271.  
  2272.    EXPORTS   ClientWndProc
  2273.  
  2274.  With this statement you're "exporting" the window procedure so that it can
  2275.  be called from another module (specifically, from the Presentation Manager).
  2276.  
  2277.  The -Gw compiler switch and the EXPORTS statement are very closely related
  2278.  and involve some manipulation on the machine-code level when control passes
  2279.  between the Presentation Manager and your program. When a program calls a
  2280.  Presentation Manager function (such as WinDispatchMsg), that function
  2281.  generally uses its own data area, which is a data segment associated with
  2282.  the dynamic link library that contains the function. In assembly-language
  2283.  terms, the function must push the current DS register (which points to the
  2284.  data segment of the program making the function call) on the stack and set
  2285.  DS to its own data segment. Before returning to the program, it pops the
  2286.  original DS off the stack. However, in the course of the WinDispatchMsg
  2287.  function call, the dynamic link library might need to call the program's
  2288.  window procedure. The DS value associated with the window procedure isn't
  2289.  directly available. Thus the window procedure would be unable to access its
  2290.  own data segment.
  2291.  
  2292.  The -Gw switch adds a special prologue and epilogue to the window procedure
  2293.  to save the value of DS (the dynamic link library's DS) on entry to the
  2294.  window procedure and restore it on exit. Exporting the window procedure
  2295.  directs OS/2 to add some code to this prologue to set DS to the program's
  2296.  data segment. Thus the window procedure can be called from the dynamic link
  2297.  library without problems.
  2298.  
  2299.  
  2300.  WELCOME1 ─ ─ Painting the Client Window
  2301.  
  2302.  Now that we have a client window with its very own window procedure that
  2303.  processes messages to the window, we are ready to process a few messages and
  2304.  paint the client window. The final WELCOME program in this chapter,
  2305.  WELCOME1, is shown in Figure 2-10.
  2306.  
  2307.  Figure 2-10.  The WELCOME1 program.
  2308.  
  2309.    The WELCOME1 File
  2310.  
  2311.    #--------------------
  2312.    # WELCOME1 make file
  2313.    #--------------------
  2314.  
  2315.    welcome1.obj : welcome1.c
  2316.         cl -c -G2sw -W3 welcome1.c
  2317.  
  2318.    welcome1.exe : welcome1.obj welcome1.def
  2319.         link welcome1, /align:16, NUL, os2, welcome1
  2320.  
  2321.    The WELCOME1.C File
  2322.  
  2323.    /*----------------------------------------------------------
  2324.       WELCOME1.C -- A Program that Writes to its Client Window
  2325.      ----------------------------------------------------------*/
  2326.  
  2327.    #define INCL_WIN
  2328.    #include <os2.h>
  2329.  
  2330.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  2331.  
  2332.    int main (void)
  2333.         {
  2334.         static CHAR  szClientClass [] = "Welcome1" ;
  2335.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  2336.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  2337.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  2338.         HAB          hab ;
  2339.         HMQ          hmq ;
  2340.         HWND         hwndFrame, hwndClient ;
  2341.         QMSG         qmsg ;
  2342.  
  2343.         hab = WinInitialize (0) ;
  2344.         hmq = WinCreateMsgQueue (hab, 0) ;
  2345.  
  2346.         WinRegisterClass (
  2347.                        hab,                // Anchor block handle
  2348.                        szClientClass,      // Name of class being registered
  2349.                        ClientWndProc,      // Window procedure for class
  2350.                        CS_SIZEREDRAW,      // Class style
  2351.                        0) ;                // Extra bytes to reserve
  2352.  
  2353.  
  2354.         hwndFrame = WinCreateStdWindow (
  2355.                        HWND_DESKTOP,       // Parent window handle
  2356.                        WS_VISIBLE,         // Style of frame window
  2357.                        &flFrameFlags,      // Pointer to control data
  2358.                        szClientClass,      // Client window class name
  2359.                        NULL,               // Title bar text
  2360.                        0L,                 // Style of client window
  2361.                        NULL,               // Module handle for resources
  2362.                        0,                  // ID of resources
  2363.                        &hwndClient) ;      // Pointer to client window handle
  2364.  
  2365.         WinSendMsg (hwndFrame, WM_SETICON,
  2366.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  2367.                     NULL) ;
  2368.  
  2369.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  2370.              WinDispatchMsg (hab, &qmsg) ;
  2371.  
  2372.         WinDestroyWindow (hwndFrame) ;
  2373.         WinDestroyMsgQueue (hmq) ;
  2374.         WinTerminate (hab) ;
  2375.         return 0 ;
  2376.         }
  2377.  
  2378.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  2379.         {
  2380.         static CHAR szText [] = "Welcome to the OS/2 Presentation Manager" ;
  2381.         HPS         hps;
  2382.         RECTL       rcl ;
  2383.  
  2384.         switch (msg)
  2385.              {
  2386.              case WM_CREATE:
  2387.                   DosBeep (261, 100) ;
  2388.                   DosBeep (330, 100) ;
  2389.                   DosBeep (392, 100) ;
  2390.                   DosBeep (523, 500) ;
  2391.                   return 0 ;
  2392.  
  2393.              case WM_PAINT:
  2394.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  2395.  
  2396.  
  2397.                   WinQueryWindowRect (hwnd, &rcl) ;
  2398.  
  2399.                   WinDrawText (hps, -1, szText, &rcl, CLR_NEUTRAL, CLR_BACKGRO
  2400.                                DT_CENTER | DT_VCENTER | DT_ERASERECT) ;
  2401.  
  2402.                   WinEndPaint (hps) ;
  2403.                   return 0 ;
  2404.  
  2405.              case WM_DESTROY:
  2406.                   DosBeep (523, 100) ;
  2407.                   DosBeep (392, 100) ;
  2408.                   DosBeep (330, 100) ;
  2409.                   DosBeep (261, 500) ;
  2410.                   return 0 ;
  2411.              }
  2412.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  2413.         }
  2414.  
  2415.    The WELCOME1.DEF File
  2416.  
  2417.    ;-------------------------------------
  2418.    ; WELCOME1.DEF module definition file
  2419.    ;-------------------------------------
  2420.  
  2421.    NAME           WELCOME1  WINDOWAPI
  2422.  
  2423.    DESCRIPTION    'Welcome to PM -- Program No. 8 (C) Charles Petzold, 1988'
  2424.    PROTMODE
  2425.    HEAPSIZE       1024
  2426.    STACKSIZE      8192
  2427.    EXPORTS        ClientWndProc
  2428.  
  2429.  
  2430.  WELCOME1.EXE displays the text "Welcome to the OS/2 Presentation Manager" in
  2431.  the center of its client window as shown in Figure 2-11.
  2432.  
  2433.  Processing Messages
  2434.  
  2435.  The ClientWndProc window procedure in WELCOME1 shows the typical switch and
  2436.  case construction used to process messages. The msg parameter to the window
  2437.  procedure identifies the message. ClientWndProc processes three messages:
  2438.  WM_CREATE, WM_PAINT, and WM_DESTROY. In most cases, a window procedure
  2439.  returns 0L when it processes a message. Any message not processed must be
  2440.  passed on to WinDefWindowProc, and the value returned from
  2441.  WinDefWindowProc must be returned from the window procedure.
  2442.  
  2443.  The WM_CREATE message is the first message that a window procedure receives.
  2444.  It is sent directly to the window procedure during the WinCreateStdWindow
  2445.  call. A window procedure can perform some window initialization during the
  2446.  WM_CREATE message. In WELCOME1, ClientWndProc calls the OS/2 DosBeep
  2447.  function to play the notes of a C-major chord to indicate that the client
  2448.  window has arrived.
  2449.  
  2450.  The WM_DESTROY message is the last message a window procedure receives. The
  2451.  Presentation Manager sends this message to the window procedure during the
  2452.  WinDestroyWindow call. Although window procedures can do some "cleanup"
  2453.  during the WM_DESTROY message, ClientWndProc again calls DosBeep a few times
  2454.  as a swan song to indicate that the client window is being destroyed.
  2455.  
  2456.  The WM_PAINT Message
  2457.  
  2458.  One of the most important messages that a window procedure receives is
  2459.  WM_PAINT, which tells the window procedure when to display something on the
  2460.  window. "What?" you say. "The Presentation Manager is telling me when I can
  2461.  display something on my window? I have to be given permission? What kind of
  2462.  fascist operating system is this?" Cool down. The WM_PAINT message is simply
  2463.  the Presentation Manager's way of telling you that a portion of your window
  2464.  is "invalid"──that is, that part of the window's visible area contains
  2465.  garbage or perhaps nothing at all. The WM_PAINT message tells the window
  2466.  function that the window is due for a paint job.
  2467.  
  2468.  How does the window become invalid? When a window is first created, the
  2469.  entire window is invalid. In fact, one of the first queued messages the
  2470.  client window receives is WM_PAINT. The window function can take this
  2471.  opportunity to display something in the window. Now suppose you minimize the
  2472.  window and then restore it to the original size. The Presentation Manager
  2473.  doesn't save the contents of the window when the window is minimized. In a
  2474.  graphical environment it's simply too much data. Thus when the window is
  2475.  restored after being minimized, the window is invalid, and a WM_PAINT
  2476.  message is placed in the message queue. If you start rearranging several
  2477.  windows on the display, a window may overlap others. The Presentation
  2478.  Manager generally won't save the area of a window covered by another window.
  2479.  When the window is uncovered, the previously hidden area is invalid, and a
  2480.  WM_PAINT message goes into the message queue.
  2481.  
  2482.  This is probably quite different from the way you usually think about using
  2483.  the video display. Under a conventional operating system, your program can
  2484.  display something on the screen whenever it wants and not worry about
  2485.  something on the screen mysteriously disappearing. Under the Presentation
  2486.  Manager, you can still──if you want──display something on a window
  2487.  whenever you want. But it makes more sense to do painting only when the
  2488.  window function receives the WM_PAINT message. The program must retain what
  2489.  it needs to recreate the appearance of the window, because it can receive a
  2490.  WM_PAINT message at almost any time. If the window function displays
  2491.  something on the window while processing a message other than WM_PAINT, it
  2492.  must also execute the same painting code when it gets a WM_PAINT message.
  2493.  
  2494.  Normally, if you resize a window to make it smaller, the window procedure
  2495.  doesn't receive a WM_PAINT message. The Presentation Manager simply cuts off
  2496.  the edges of the window that previously extended past the new size. You'll
  2497.  note, however, that in WELCOME1 the fourth parameter to WinRegisterClass is
  2498.  set to CS_SIZEREDRAW. This is a class style. It causes the Presentation
  2499.  Manager to invalidate the entire window and post a WM_PAINT message to the
  2500.  client window whenever the size of the window changes.
  2501.  
  2502.  When you get a WM_PAINT message, you can obtain the coordinates of the
  2503.  invalid area of the window. You need update only that part of the window.
  2504.  We'll explore this and other aspects of the WM_PAINT message more in
  2505.  upcoming chapters. Right now all you have to know is that WM_PAINT informs
  2506.  the window procedure that it's time to update the appearance of the window.
  2507.  
  2508.  Processing WM_PAINT
  2509.  
  2510.  The code that processes the WM_PAINT message in a window function must begin
  2511.  with a call to WinBeginPaint and end with a call to WinEndPaint. When
  2512.  WinEndPaint is called, the Presentation Manager validates the entire area of
  2513.  the window. Using a simple form of the WinBeginPaint call, the code looks
  2514.  like this:
  2515.  
  2516.    case WM_PAINT:
  2517.         hps = WinBeginPaint (hwnd, NULL, NULL) ;
  2518.              [paint the window]
  2519.         WinEndPaint (hps) ;
  2520.         return 0 ;
  2521.  
  2522.  If your program doesn't process WM_PAINT messages (as WELCOME doesn't), they
  2523.  are passed to WinDefWindowProc. WinDefWindowProc simply calls
  2524.  WinBeginPaint and WinEndPaint (with nothing in between) to validate the
  2525.  entire area of the client window. This is a good example of how
  2526.  WinDefWindowProc takes care of chores a program chooses to ignore. If
  2527.  WinBeginPaint and WinEndPaint aren't called during a WM_PAINT message, an
  2528.  area of the window remains invalid, and the WM_PAINT message isn't removed
  2529.  from the message queue.
  2530.  
  2531.  The handle returned from the WinBeginPaint call is a handle to a
  2532.  presentation space. The handle is stored in a variable named hps of type
  2533.  HPS. You need this handle to the presentation space to draw on the surface
  2534.  of the client window. The presentation space handle is the first parameter
  2535.  to all the Graphics Programming Interface (GPI) drawing functions.
  2536.  
  2537.  The presentation space is essentially a data structure that defines an
  2538.  abstract display surface. The presentation space is associated with a
  2539.  "device context," which defines a particular physical display medium. In the
  2540.  form of the WinBeginPaint call used here, the presentation space for which
  2541.  we get a handle is associated with a device context for the video display──
  2542.  in particular, the part of the display that the client window occupies. This
  2543.  form of the WinBeginPaint call (with the second parameter set to NULL)
  2544.  implies that we're using a subset of GPI that is called the "cached
  2545.  micro-PS."
  2546.  
  2547.  Painting WELCOME1's Client Window
  2548.  
  2549.  After the WinBeginPaint call, WELCOME1 obtains the dimensions of the client
  2550.  window by using this function:
  2551.  
  2552.    WinQueryWindowRect (hwnd, &rcl) ;
  2553.  
  2554.  The first parameter is hwnd, the handle to the client window. The rcl
  2555.  variable is a structure of type RECTL (rectangle). The RECTL structure has
  2556.  four fields: xLeft, yBottom, xRight, and yTop. WinQueryWindowRect fills the
  2557.  fields of the rcl structure with the current coordinates of the client
  2558.  window. These coordinates are relative to the lower-left corner of the
  2559.  window; hence the xLeft and yBottom fields are set to 0. The xRight field is
  2560.  actually the width of the window in pixels, and yTop is the height of the
  2561.  window in pixels.
  2562.  
  2563.  The WinDrawText function is used to display the string "Welcome to the OS/2
  2564.  Presentation Manager" in the center of the client window. It uses the rcl
  2565.  rectangle structure and the parameter DT_CENTER | DT_VCENTER | DT_ERASERECT
  2566.  to specify that the string is to be horizontally and vertically centered
  2567.  within the rectangle and that the rectangle (the entire window) is to be
  2568.  erased before the text is displayed. The CLR_NEUTRAL and CLR_BACKGROUND
  2569.  parameters specify the text color and background color. I'll discuss these
  2570.  two "colors" in Chapter 5.
  2571.  
  2572.  Too Much Overhead?
  2573.  
  2574.  This has been a long journey to write a simple program that displays some
  2575.  text and plays a tune. But we've basically covered all the facets of the
  2576.  Presentation Manager. You've learned about windows. You've learned about
  2577.  messages. You've learned about presentation spaces. That's it. Everything
  2578.  that follows is just detail.
  2579.  
  2580.  
  2581.  Chapter 3  More Fun With Windows
  2582.  ───────────────────────────────────────────────────────────────────────────
  2583.  
  2584.  
  2585.  In Chapter 2, our rush to create a functional window required that we ignore
  2586.  some details and finer points of the art of window creation. Here we'll
  2587.  explore variations on the basic theme.
  2588.  
  2589.  
  2590.  Exploring the Standard Window
  2591.  
  2592.  The WinCreateStdWindow function creates one or more windows. In the final
  2593.  version of WELCOME1, shown in Chapter 2, WinCreateStdWindow creates five
  2594.  windows──the frame, title bar, system menu, minimize/maximize window, and
  2595.  the client window. The term "standard window" refers to this collection of
  2596.  windows organized around the frame window. All but one of the windows that
  2597.  make up the standard window are created based on window classes already
  2598.  registered by the Presentation Manager. Messages to these windows come
  2599.  through the program's message queue but are dispatched to the particular
  2600.  window procedure in PMWIN.DLL that is defined by the window class. The
  2601.  client window, on the other hand, is generally based on a window class that
  2602.  the program itself registers, and it uses a window procedure within the
  2603.  program (called ClientWndProc in WELCOME1) to process its messages.
  2604.  
  2605.  The windows that make up the standard window receive messages from the
  2606.  Presentation Manager (often initiated by user input) but can also send
  2607.  messages to one another. They essentially carry on a family conversation.
  2608.  
  2609.  The Family of Windows
  2610.  
  2611.  Windows created in the Presentation Manager usually have a parent-child
  2612.  relationship. In the standard window, the frame window is the parent, and
  2613.  the other windows (including the client window) are the children of the
  2614.  frame window. Thus we can define the term standard window as "a frame window
  2615.  and its children." Windows with a common parent are called "sibling
  2616.  windows." A window can have many children but only one parent. A window's
  2617.  children, its children's children, and so forth are called the window's
  2618.  "descendants."
  2619.  
  2620.  The grand matriarch of Presentation Manager windows is the "desktop window."
  2621.  The desktop window occupies the entire screen. Although it appears to be
  2622.  simply a background color, the desktop window is a real window with a window
  2623.  procedure in PMWIN.DLL named WinDesktopWndProc that processes its messages.
  2624.  Every other window is a descendant of the desktop window. (This isn't quite
  2625.  true. Some windows, called "object windows," have no parent. Like other
  2626.  windows, an object window can send and receive messages, but an object
  2627.  window isn't visible on the screen and doesn't receive user input. When I
  2628.  discuss windows in this book I'm usually talking about nonobject windows.)
  2629.  
  2630.  A child of the desktop window is called a "top-level window." Virtually
  2631.  every program that runs under the Presentation Manager creates at least one
  2632.  top-level window. When a program such as WELCOME1 calls WinCreateStdWindow
  2633.  to create the application's main window, the frame window is a top-level
  2634.  window. The other windows created by the function are children of the frame
  2635.  window and are not top-level windows. The family tree for the WELCOME1
  2636.  program is shown in Figure 3-1.
  2637.  
  2638.  A child window is affected by its parent in several ways:
  2639.  
  2640.    ■  A child window is always displayed within the area of the screen
  2641.       occupied by its parent. We say that the child is "clipped" on the area
  2642.       of its parent. This is fairly obvious in the case of the desktop window
  2643.       and the frame window because the desktop window encompasses the entire
  2644.       screen. The children of the frame window also appear within the area
  2645.       occupied by the frame. If the frame window tried to position part of
  2646.       the title bar window outside of the area that is occupied by the frame,
  2647.       the part of the title bar outside the frame window would not be
  2648.       visible.
  2649.  
  2650.    ■  Child windows remain in the same position relative to the parent unless
  2651.       explicitly moved. When you move the frame window around the screen, the
  2652.       children follow. This happens automatically: When the frame window
  2653.       wants to move itself (usually because it has received a message from
  2654.       the title bar window that the user has moved the window), it need only
  2655.       tell the Presentation Manager to move the frame. The Presentation
  2656.       Manager takes care of moving the children.
  2657.  
  2658.    ■  When a parent window is hidden, minimized, or destroyed, all of its
  2659.       children (and, by extension, all its descendants) are also hidden,
  2660.       minimized, or destroyed. This should be partly obvious in WELCOME1. If
  2661.       you minimize the frame window, all the children of the frame window are
  2662.       also removed from the screen. When the frame window is destroyed by the
  2663.       call to WinDestroyWindow after WELCOME1 leaves the message loop, all
  2664.       the children of the frame window (including the client window) are also
  2665.       destroyed. ClientWndProc receives a WM_DESTROY message at that time.
  2666.  
  2667.    ■  Sibling windows can overlap on the screen. We'll see examples of
  2668.       overlapping siblings in the WELCOME2 and WELCOME3 programs in this
  2669.       chapter.
  2670.  
  2671.  The Presentation Manager includes a function, WinQueryWindow, that you can
  2672.  use to determine a window's parent:
  2673.  
  2674.    hwndParent = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;
  2675.  
  2676.  The variable hwndParent is set to the handle of the parent window of hwnd.
  2677.  For example, after the WinCreateStdWindow function returns control to your
  2678.  program, the following call obtains the frame window handle:
  2679.  
  2680.    hwndFrame = WinQueryWindow (hwndClient, QW_PARENT, FALSE) ;
  2681.  
  2682.  This will be the same window handle returned from WinCreateStdWindow.
  2683.  
  2684.  Figure 3-1.  The WELCOME1 family tree.
  2685.  
  2686.                          Desktop
  2687.                          Window
  2688.                             │                          ─────┐
  2689.                           Frame                             │
  2690.                           Window                            │
  2691.                        (hwndFrame)                          │
  2692.                 ┌───────────┼───────────┬────────────┐      │ Standard
  2693.                 │           │           │            │      │ window
  2694.                 │           │           │            │      │
  2695.               Title       Client      System      Minimize/ │
  2696.                bar        window       menu       maximize  │
  2697.               window   (hwndClient)   window       window   │
  2698.                                                         ────┘
  2699.  
  2700.  
  2701.  If hwndFrame is a top-level window, you can obtain the desktop window handle
  2702.  by calling
  2703.  
  2704.    hwndDesktop = WinQueryWindow (hwndFrame, QW_PARENT, FALSE) ;
  2705.  
  2706.  Or you can use the function specifically designed for this purpose:
  2707.  
  2708.    hwndDesktop = WinQueryDesktopWindow (hab, NULL) ;
  2709.  
  2710.  In many Presentation Manager functions, the HWND_DESKTOP identifier is used
  2711.  to refer to the desktop window. Usually you pass HWND_DESKTOP as the first
  2712.  parameter to WinCreateStdWindow. This makes the frame a top-level window.
  2713.  The application often has no choice but to do this: The frame window must
  2714.  have a parent, but the application doesn't know about any other windows
  2715.  except the desktop window. The desktop is thus the only possible parent.
  2716.  
  2717.  If the program calls WinCreateStdWindow a second time, it has a choice: The
  2718.  second frame window could be another top-level window, or it could be a
  2719.  child of one of the windows created in the first WinCreateStdWindow call
  2720.  (most likely a child of the first client window). Let's look at an example
  2721.  of the first approach.
  2722.  
  2723.  Creating Multiple Top-Level Windows
  2724.  
  2725.  The WELCOME2 program, shown in Figure 3-2, creates two top-level standard
  2726.  windows. The program contains two window procedures (Client1WndProc and
  2727.  Client2WndProc), registers two window classes ("Welcome2.1" and
  2728.  "Welcome2.2"), and calls WinCreateStdWindow twice. Note that the EXPORTS
  2729.  section of WELCOME2.DEF lists both window procedures.
  2730.  
  2731.  Figure 3-2.  The WELCOME2 program.
  2732.  
  2733.    The WELCOME2 File
  2734.  
  2735.    #--------------------
  2736.    # WELCOME2 make file
  2737.    #--------------------
  2738.  
  2739.    welcome2.obj : welcome2.c
  2740.         cl -c -G2sw -W3 welcome2.c
  2741.  
  2742.    welcome2.exe : welcome2.obj welcome2.def
  2743.         link welcome2, /align:16, NUL, os2, welcome2
  2744.  
  2745.    The WELCOME2.C File
  2746.  
  2747.    /*------------------------------------------------------------
  2748.       WELCOME2.C -- A Program that Creates Two Top-Level Windows
  2749.      ------------------------------------------------------------*/
  2750.  
  2751.    #define INCL_WIN
  2752.    #include <os2.h>
  2753.  
  2754.    MRESULT EXPENTRY Client1WndProc (HWND, USHORT, MPARAM, MPARAM) ;
  2755.    MRESULT EXPENTRY Client2WndProc (HWND, USHORT, MPARAM, MPARAM) ;
  2756.  
  2757.    int main (void)
  2758.         {
  2759.         static CHAR  szClientClass1 [] = "Welcome2.1",
  2760.                      szClientClass2 [] = "Welcome2.2" ;
  2761.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  2762.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  2763.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  2764.         HAB          hab ;
  2765.         HMQ          hmq ;
  2766.         HWND         hwndFrame1, hwndFrame2, hwndClient1, hwndClient2 ;
  2767.         QMSG         qmsg ;
  2768.  
  2769.         hab = WinInitialize (0) ;
  2770.         hmq = WinCreateMsgQueue (hab, 0) ;
  2771.  
  2772.         WinRegisterClass (
  2773.                        hab,                // Anchor block handle
  2774.                        szClientClass1,     // Name of class being registered
  2775.                        Client1WndProc,     // Window procedure for class
  2776.                        CS_SIZEREDRAW,      // Class style
  2777.                        0) ;                // Extra bytes to reserve
  2778.  
  2779.         WinRegisterClass (
  2780.                        hab,                // Anchor block handle
  2781.                        szClientClass2,     // Name of class being registered
  2782.                        Client2WndProc,     // Window procedure for class
  2783.                        CS_SIZEREDRAW,      // Class style
  2784.                        0) ;                // Extra bytes to reserve
  2785.  
  2786.         hwndFrame1 = WinCreateStdWindow (
  2787.                        HWND_DESKTOP,       // Parent window handle
  2788.                        WS_VISIBLE,         // Style of frame window
  2789.                        &flFrameFlags,      // Pointer to control data
  2790.  
  2791.                        szClientClass1,     // Client window class name
  2792.                        NULL,               // Title bar text
  2793.                        0L,                 // Style of client window
  2794.                        NULL,               // Module handle for resources
  2795.                        0,                  // ID of resources
  2796.                        &hwndClient1) ;     // Pointer to client window handle
  2797.  
  2798.         hwndFrame2 = WinCreateStdWindow (
  2799.                        HWND_DESKTOP,       // Parent window handle
  2800.                        WS_VISIBLE,         // Style of frame window
  2801.                        &flFrameFlags,      // Pointer to control data
  2802.                        szClientClass2,     // Client window class name
  2803.                        " - Window No. 2",  // Title bar text
  2804.                        0L,                 // Style of client window
  2805.                        NULL,               // Module handle for resources
  2806.                        0,                  // ID of resources
  2807.                        &hwndClient2) ;     // Pointer to client window handle
  2808.  
  2809.         WinSendMsg (hwndFrame1, WM_SETICON,
  2810.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  2811.                     NULL) ;
  2812.  
  2813.         WinSendMsg (hwndFrame2, WM_SETICON,
  2814.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  2815.                     NULL) ;
  2816.  
  2817.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  2818.              WinDispatchMsg (hab, &qmsg) ;
  2819.  
  2820.         WinDestroyWindow (hwndFrame1) ;
  2821.         WinDestroyWindow (hwndFrame2) ;
  2822.         WinDestroyMsgQueue (hmq) ;
  2823.         WinTerminate (hab) ;
  2824.         return 0 ;
  2825.         }
  2826.  
  2827.    MRESULT EXPENTRY Client1WndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM
  2828.         {
  2829.         static CHAR szText [] = "Welcome to Window No. 1" ;
  2830.         HPS         hps ;
  2831.         RECTL       rcl ;
  2832.  
  2833.         switch (msg)
  2834.              {
  2835.              case WM_PAINT:
  2836.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  2837.  
  2838.                   WinQueryWindowRect (hwnd, &rcl) ;
  2839.  
  2840.                   WinDrawText (hps, -1, szText, &rcl, CLR_NEUTRAL, CLR_BACKGRO
  2841.                                DT_CENTER | DT_VCENTER | DT_ERASERECT) ;
  2842.  
  2843.                   WinEndPaint (hps) ;
  2844.                   return 0 ;
  2845.              }
  2846.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  2847.         }
  2848.  
  2849.    MRESULT EXPENTRY Client2WndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM
  2850.         {
  2851.         static CHAR szText [] = "Welcome to Window No. 2" ;
  2852.         HPS         hps ;
  2853.         RECTL       rcl ;
  2854.  
  2855.         switch (msg)
  2856.              {
  2857.              case WM_PAINT:
  2858.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  2859.  
  2860.                   WinQueryWindowRect (hwnd, &rcl) ;
  2861.  
  2862.                   WinDrawText (hps, -1, szText, &rcl, CLR_NEUTRAL, CLR_BACKGRO
  2863.                                DT_CENTER | DT_VCENTER | DT_ERASERECT) ;
  2864.  
  2865.                   WinEndPaint (hps) ;
  2866.                   return 0 ;
  2867.  
  2868.              case WM_CLOSE:
  2869.                   return 0 ;
  2870.              }
  2871.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  2872.         }
  2873.  
  2874.    The WELCOME2.DEF File
  2875.  
  2876.    ;-------------------------------------
  2877.    ; WELCOME2.DEF module definition file
  2878.    ;-------------------------------------
  2879.  
  2880.    NAME           WELCOME2  WINDOWAPI
  2881.  
  2882.    DESCRIPTION    'Creates Two Top-Level Windows (C) Charles Petzold, 1988'
  2883.    PROTMODE
  2884.    HEAPSIZE       1024
  2885.    STACKSIZE      8192
  2886.    EXPORTS        Client1WndProc
  2887.                   Client2WndProc
  2888.  
  2889.  
  2890.  The first parameter in the WinCreateStdWindow call is the parent of the
  2891.  frame window. In both function calls, this parameter is set to HWND_DESKTOP.
  2892.  The two frame windows in WELCOME2 share the same parent and are thus
  2893.  siblings. The WELCOME2 family tree is shown in Figure 3-3.
  2894.  
  2895.  When you run WELCOME2 (as shown in Figure 3-4), you'll find that the two
  2896.  top-level windows function independently, almost as if they were created in
  2897.  different programs. Both windows are listed on the Task Manager. Because all
  2898.  top-level windows are siblings, top-level windows overlap. Only one
  2899.  top-level window is "active" at any time. You can switch between the two
  2900.  windows (as you can switch among all top-level windows listed on the Task
  2901.  Manager) using the Alt-Esc or Alt-Tab key combinations.
  2902.  
  2903.  Figure 3-3.  The WELCOME2 family tree.
  2904.  
  2905.                               Desktop
  2906.                               window
  2907.                                  │
  2908.                                  │
  2909.             ┌────────────────────┴─────────────────┐
  2910.             │                                      │
  2911.           Frame                                  Frame
  2912.           window                                 window
  2913.        (hwndFrame1)                           (hwndFrame2)
  2914.             │                                      │
  2915.    ┌────────┼────────┬──────────┐         ┌────────┼────────┬──────────┐
  2916.    │        │        │          │         │        │        │          │
  2917.  Title    Client   System    Minimize/  Title    Client   System    Minimize/
  2918.   bar     window    menu     maximize    bar     window    menu     maximize
  2919.  window  (hwnd-    window  window       window  (hwnd-    window     window
  2920.          Client1)                               Client2)
  2921.  
  2922.  
  2923.  To simplify this demonstration, I have both window functions in WELCOME2
  2924.  perform approximately the same task (display some text in the client
  2925.  window). However, the two window functions could perform entirely different
  2926.  tasks from one another. For example, it's not difficult to imagine a
  2927.  Presentation Manager CAD (computer-assisted design) program organized into
  2928.  two top-level windows. One window could be an ASCII text editor and allow
  2929.  you to enter and edit a series of drawing commands. The other window could
  2930.  display the graphical representation of these commands. When you change one
  2931.  of the commands in the editor window, the change could be reflected in the
  2932.  graphics window; likewise, if you change the drawing itself (perhaps using
  2933.  the mouse), the change could be reflected in the corresponding text command
  2934.  in the editor window. The two client window procedures would communicate
  2935.  these changes to each other with messages. You would store the two client
  2936.  window handles returned from the WinCreateStdWindow calls in global
  2937.  variables so that they could be accessed by both window procedures.
  2938.  
  2939.  What messages would the two client windows send to each other? That's up to
  2940.  you. PMWIN.H defines the identifier WM_USER specifically for the purpose of
  2941.  creating your own messages. Within a program, you can define private
  2942.  messages that use values of WM_USER or above.
  2943.  
  2944.    #define WM_MYMESSAGE0  (WM_USER + 0)
  2945.    #define WM_MYMESSAGE1  (WM_USER + 1)
  2946.    #define WM_MYMESSAGE2  (WM_USER + 2)
  2947.  
  2948.  If Client1WndProc needs to send a WM_MYMESSAGE1 message to Client2WndProc,
  2949.  it can do so:
  2950.  
  2951.    WinSendMsg (hwndClient2, WM_MYMESSAGE1, MPFROMLONG (lData1),
  2952.                MPFROMLONG (lData2)) ;
  2953.  
  2954.  lData1 and lData2 are long integers with message-specific data. The
  2955.  MPFROMLONG macros convert a long integer to an MPARAM data type. The message
  2956.  would be processed within Client2WndProc like this:
  2957.  
  2958.    case WM_MYMESSAGE1:
  2959.              [process message]
  2960.         return 0 ;
  2961.  
  2962.  Keep in mind that the two MPARAM values that accompany messages can be far
  2963.  pointers to structures or to big blocks of memory, so the amount of data
  2964.  passed in the message can be very large. The value returned from WinSendMsg
  2965.  is the value that the window procedure returns once it has processed the
  2966.  message. This is defined as an MRESULT, which is also a far pointer.
  2967.  
  2968.  Title Bar Text
  2969.  
  2970.  Notice that in Figure 3-4 the first window's title bar contained the text
  2971.  "WELCOME2.EXE" and the second had "WELCOME2.EXE──Window No. 2." This is what
  2972.  you'll see when you run the program from the CMD.EXE prompt or the
  2973.  Presentation Manager File System. If you install WELCOME2 in the Start
  2974.  Programs window and run the program that way, you'll see the program title
  2975.  specified in Start Programs in place of "WELCOME2.EXE" on the title bar of
  2976.  each of the windows.
  2977.  
  2978.  This is part of what the FCF_TASKLIST flag does. The title bar text (and the
  2979.  Task Manager entry) is the name under which the program was started,
  2980.  concatenated with the title bar text specified in the WinCreateStdWindow
  2981.  function. For most programs in this book, I use NULL for the
  2982.  WinCreateStdWindow parameter that indicates the title bar text. This causes
  2983.  the title bar to display only the .EXE filename or the program title from
  2984.  Start Programs. For the second window in WELCOME2, however, I used "──Window
  2985.  No. 2" in WinCreateStdWindow, so this text also appears in the title bar and
  2986.  on the Task Manager.
  2987.  
  2988.  Terminating a Presentation Manager Program
  2989.  
  2990.  I've written WELCOME2 so that you cannot terminate the program by selecting
  2991.  "Close" from the second window's system menu. This requires a little
  2992.  explanation of how Presentation Manager programs terminate. When you select
  2993.  Close from the system menu, the client window procedure receives a WM_CLOSE
  2994.  message. If the window procedure passes WM_CLOSE to WinDefWindowProc, the
  2995.  Presentation Manager posts a WM_QUIT message to the message queue. This
  2996.  causes WinGetMsg to return 0 when the WM_QUIT message is retrieved from the
  2997.  queue, and the program exits the message loop. If a window procedure simply
  2998.  traps WM_CLOSE messages and returns from the window procedure without
  2999.  calling WinDefWindowProc, then nothing happens. This is how Client2WndProc
  3000.  essentially disables the Close option on its system menu. (However, you can
  3001.  terminate the program by closing either of the two windows from the Task
  3002.  Manager. The Task Manager simply posts a WM_QUIT message to the message
  3003.  queue. I describe how to process this WM_QUIT message in Chapter 13.)
  3004.  
  3005.  WELCOME2 is somewhat unorthodox. A Presentation Manager program usually
  3006.  creates only one top-level main window. Any other top-level windows created
  3007.  in the program (such as dialog boxes) exist for only short periods of time.
  3008.  
  3009.  Creating Children of the Client
  3010.  
  3011.  A more common approach to creating multiple standard windows is demonstrated
  3012.  in the WELCOME3 program, shown in Figure 3-5.
  3013.  
  3014.  Figure 3-5.  The WELCOME3 program.
  3015.  
  3016.    The WELCOME3 File
  3017.  
  3018.    #--------------------
  3019.    # WELCOME3 make file
  3020.    #--------------------
  3021.  
  3022.    welcome3.obj : welcome3.c
  3023.         cl -c -G2sw -W3 welcome3.c
  3024.  
  3025.    welcome3.exe : welcome3.obj welcome3.def
  3026.         link welcome3, /align:16, NUL, os2, welcome3
  3027.  
  3028.    The WELCOME3.C File
  3029.  
  3030.    /*-----------------------------------------------------------
  3031.       WELCOME3.C -- Creates a Top-Level Window and Two Children
  3032.      -----------------------------------------------------------*/
  3033.  
  3034.    #define INCL_WIN
  3035.    #include <os2.h>
  3036.  
  3037.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  3038.    MRESULT EXPENTRY ChildWndProc  (HWND, USHORT, MPARAM, MPARAM) ;
  3039.  
  3040.    int main (void)
  3041.         {
  3042.         static CHAR  szClientClass [] = "Welcome3",
  3043.                      szChildClass  [] = "Welcome3.Child" ;
  3044.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
  3045.                                     FCF_SIZEBORDER    | FCF_MINMAX   |
  3046.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  3047.         HAB          hab ;
  3048.         HMQ          hmq ;
  3049.         HWND         hwndFrame,  hwndChildFrame1,  hwndChildFrame2,
  3050.                      hwndClient, hwndChildClient1, hwndChildClient2 ;
  3051.         QMSG         qmsg ;
  3052.  
  3053.         hab = WinInitialize (0) ;
  3054.         hmq = WinCreateMsgQueue (hab, 0) ;
  3055.  
  3056.         WinRegisterClass (
  3057.                        hab,                // Anchor block handle
  3058.                        szClientClass,      // Name of class being registered
  3059.                        ClientWndProc,      // Window procedure for class
  3060.                        CS_SIZEREDRAW,      // Class style
  3061.                        0) ;                // Extra bytes to reserve
  3062.  
  3063.         WinRegisterClass (
  3064.                        hab,                // Anchor block handle
  3065.                        szChildClass,       // Name of class being registered
  3066.                        ChildWndProc,       // Window procedure for class
  3067.                        CS_SIZEREDRAW,      // Class style
  3068.                        sizeof (PVOID)) ;   // Extra bytes to reserve
  3069.  
  3070.              /*-------------------------
  3071.                 Create top-level window
  3072.                -------------------------*/
  3073.  
  3074.         hwndFrame = WinCreateStdWindow (
  3075.                        HWND_DESKTOP,       // Parent window handle
  3076.                        WS_VISIBLE,         // Style of frame window
  3077.                        &flFrameFlags,      // Pointer to control data
  3078.                        szClientClass,      // Client window class name
  3079.                        NULL,               // Title bar text
  3080.                        0L,                 // Style of client window
  3081.                        NULL,               // Module handle for resources
  3082.                        0,                  // ID of resources
  3083.                        &hwndClient) ;      // Pointer to client window handle
  3084.  
  3085.         WinSendMsg (hwndFrame, WM_SETICON,
  3086.                          WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE
  3087.                          NULL) ;
  3088.  
  3089.              /*--------------------------
  3090.                 Create two child windows
  3091.                --------------------------*/
  3092.  
  3093.         flFrameFlags &= ~FCF_TASKLIST ;
  3094.  
  3095.         hwndChildFrame1 = WinCreateStdWindow (
  3096.                        hwndClient,         // Parent window handle
  3097.                        WS_VISIBLE,         // Style of frame window
  3098.                        &flFrameFlags,      // Pointer to control data
  3099.                        szChildClass,       // Client window class name
  3100.                        "Child No. 1",      // Title bar text
  3101.                        0L,                 // Style of client window
  3102.                        NULL,               // Module handle for resources
  3103.                        0,                  // ID of resources
  3104.                        &hwndChildClient1) ;// Pointer to client window handle
  3105.  
  3106.         hwndChildFrame2 = WinCreateStdWindow (
  3107.                        hwndClient,         // Parent window handle
  3108.                        WS_VISIBLE,         // Style of frame window
  3109.                        &flFrameFlags,      // Pointer to control data
  3110.                        szChildClass,       // Client window class name
  3111.                        "Child No. 2",      // Title bar text
  3112.                        0L,                 // Style of client window
  3113.                        NULL,               // Module handle for resources
  3114.                        0,                  // ID of resources
  3115.                        &hwndChildClient2) ;// Pointer to client window handle
  3116.  
  3117.         WinSendMsg (hwndChildFrame1, WM_SETICON,
  3118.                          WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE
  3119.                          NULL) ;
  3120.  
  3121.         WinSendMsg (hwndChildFrame2, WM_SETICON,
  3122.                          WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE
  3123.                          NULL) ;
  3124.  
  3125.              /*-----------------------------------------------------
  3126.                 Set reserved area of window to text string pointers
  3127.                -----------------------------------------------------*/
  3128.  
  3129.         WinSetWindowPtr (hwndChildClient1, QWL_USER, "I'm a child ...") ;
  3130.         WinSetWindowPtr (hwndChildClient2, QWL_USER, "... Me too!") ;
  3131.  
  3132.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  3133.              WinDispatchMsg (hab, &qmsg) ;
  3134.  
  3135.         WinDestroyWindow (hwndFrame) ;
  3136.         WinDestroyMsgQueue (hmq) ;
  3137.         WinTerminate (hab) ;
  3138.         return 0 ;
  3139.         }
  3140.  
  3141.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  3142.         {
  3143.         static CHAR szText [] = "I'm the parent of two children" ;
  3144.         HPS         hps ;
  3145.         RECTL       rcl ;
  3146.  
  3147.         switch (msg)
  3148.              {
  3149.              case WM_PAINT:
  3150.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  3151.  
  3152.                   WinQueryWindowRect (hwnd, &rcl) ;
  3153.  
  3154.                   WinDrawText (hps, -1, szText, &rcl, CLR_NEUTRAL, CLR_BACKGRO
  3155.                                DT_CENTER | DT_VCENTER | DT_ERASERECT) ;
  3156.  
  3157.                   WinEndPaint (hps) ;
  3158.                   return 0 ;
  3159.              }
  3160.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  3161.         }
  3162.  
  3163.    MRESULT EXPENTRY ChildWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp
  3164.         {
  3165.         HPS   hps ;
  3166.         RECTL rcl ;
  3167.  
  3168.         switch (msg)
  3169.              {
  3170.              case WM_PAINT:
  3171.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  3172.  
  3173.                   WinQueryWindowRect (hwnd, &rcl) ;
  3174.  
  3175.                   WinDrawText (hps, -1, WinQueryWindowPtr (hwnd, QWL_USER), &r
  3176.                                CLR_NEUTRAL, CLR_BACKGROUND,
  3177.                                DT_CENTER | DT_VCENTER | DT_ERASERECT) ;
  3178.  
  3179.                   WinEndPaint (hps) ;
  3180.                   return 0 ;
  3181.  
  3182.              case WM_CLOSE:
  3183.                   WinDestroyWindow (WinQueryWindow (hwnd, QW_PARENT, FALSE)) ;
  3184.                   return 0 ;
  3185.              }
  3186.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  3187.         }
  3188.  
  3189.    The WELCOME3.DEF File
  3190.  
  3191.    ;-------------------------------------
  3192.    ; WELCOME3.DEF module definition file
  3193.    ;-------------------------------------
  3194.  
  3195.    NAME           WELCOME3  WINDOWAPI
  3196.  
  3197.    DESCRIPTION    'Creates Top-Level and Two Children (C) Charles Petzold, 198
  3198.    PROTMODE
  3199.    HEAPSIZE       1024
  3200.    STACKSIZE      8192
  3201.    EXPORTS        ClientWndProc
  3202.                   ChildWndProc
  3203.  
  3204.  
  3205.  WELCOME3 makes three calls to WinCreateStdWindow. The first call creates a
  3206.  top-level window. The second and third calls create child standard windows
  3207.  of the first client window. For these children, the first parameter to
  3208.  WinCreateStdWindow is hwndClient──the client window handle returned from
  3209.  the first call. The second and third standard windows are siblings. Both
  3210.  client windows of these children are based on the same window class
  3211.  ("Welcome3.Child") and thus share the same window procedure, ChildWndProc,
  3212.  but they could easily be based on different window classes. Figure 3-6
  3213.  shows the WELCOME3 family tree, and Figure 3-7 shows the program running
  3214.  under the Presentation Manager.
  3215.  
  3216.  Figure 3-6.  The WELCOME3 family tree.
  3217.  
  3218.                               Desktop
  3219.                               Window
  3220.                                  │
  3221.                                Frame
  3222.                                Window
  3223.                             (hwndFrame)
  3224.                      ┌───────────┼───────────┬────────────┐
  3225.                      │           │           │            │
  3226.                      │           │           │            │
  3227.                    Title       Client      System      Minimize/
  3228.                     bar        window       menu       maximize
  3229.                    window   (hwndClient)   window       window
  3230.                                  │
  3231.             ┌────────────────────┴──────────────────┐
  3232.             │                                       │
  3233.           Frame                                   Frame
  3234.           window                                  window
  3235.     (hwndChildFrame1)                       (hwndChildFrame2)
  3236.             │                                       │
  3237.    ┌────────┼────────┬──────────┐            ┌──────┼────────┬───────┐
  3238.    │        │        │          │            │      │        │       │
  3239.  Title    Client   System    Minimize/     Title  Client   System Minimize/
  3240.   bar     window    menu     maximize       bar   window    menu  maximize
  3241.  window  (hwnd-    window     window     window  (hwnd-    window     window
  3242.           Child-                                  Child-
  3243.          Client1)                                  Client2)
  3244.  
  3245.  
  3246.  This is the more common technique for creating multiple windows within a
  3247.  program and is the basis for the Multiple Document Interface (MDI)
  3248.  convention used by the File System program. The top-level window is the
  3249.  application's main window. The client window of this top-level standard
  3250.  window is the application's work space. Several other child windows can
  3251.  exist within this work space.
  3252.  
  3253.  You'll notice that the two child standard windows obey the rules for child
  3254.  windows discussed earlier: They can be displayed only within the area
  3255.  occupied by their parent; because they are siblings, they can overlap; they
  3256.  follow the parent when the parent is moved around the screen; and they are
  3257.  minimized when the parent is minimized. You can also independently minimize
  3258.  these two children──their windows will still appear within the area of the
  3259.  parent. This is analogous to the organization and display of top-level
  3260.  windows relative to the desktop window.
  3261.  
  3262.  Although you can use the Alt-Esc or Alt-Tab key combination to move between
  3263.  the top-level windows in the Presentation Manger, there is no automatic
  3264.  keyboard interface for moving between windows that are not top-level
  3265.  windows. The program would have to provide this keyboard interface. However,
  3266.  you can bring a particular child standard window to the top by clicking on
  3267.  its window with the mouse.
  3268.  
  3269.  The two child windows are not listed on the Task Manager. Only top-level
  3270.  windows can be listed there. Before creating these child windows, WELCOME3
  3271.  removes the FCF_TASKLIST flag from flFrameFlags:
  3272.  
  3273.        flFrameFlags & = ~FCF_TASKLIST ;
  3274.  
  3275.  This also causes the title bar text for the child windows to be exactly what
  3276.  is specified in the WinCreateStdWindow function.
  3277.  
  3278.  WELCOME3 uses a little trick that allows the two child standard windows to
  3279.  display different text in their client windows. When the program registers
  3280.  the "Welcome3.Child" window class, it specifies that 4 bytes (the size of a
  3281.  PVOID or far pointer) are to be reserved for use by the program for every
  3282.  window created based on this class. This is indicated by the fifth parameter
  3283.  to WinRegisterClass:
  3284.  
  3285.    WinRegisterClass (
  3286.                   hab,                // Anchor block handle
  3287.                   szChildClass,       // Name of class being registered
  3288.                   ChildWndProc,       // Window procedure for class
  3289.                   CS_SIZEREDRAW,      // Class style
  3290.                   sizeof (PVOID);     // Extra bytes to reserve
  3291.  
  3292.  After the two child standard windows are created, WELCOME3 uses
  3293.  WinSetWindowPtr to store something in that area:
  3294.  
  3295.    WinSetWindowPtr (hwndChildClient1, QWL_USER, "I'm a child ...") ;
  3296.    WinSetWindowPtr (hwndChildClient2, QWL_USER, "... Me too!") ;
  3297.  
  3298.  What is stored in this space is actually the long (or far) address of the
  3299.  static text strings "I'm a child ..." and "... Me too!" In ChildWndProc,
  3300.  these addresses are retrieved during processing of the WM_PAINT message and
  3301.  passed to the WinDrawText function:
  3302.  
  3303.    WinDrawText (hPS, -1, WinQueryWindowPtr (hwnd, QWL_USER), &rcl,
  3304.                CLR_NEUTRAL, CLR_BACKGROUND,
  3305.                DT_CENTER | DT_VCENTER | DT_ERASERECT) ;
  3306.  
  3307.  Thus the window procedure doesn't have to figure out which child window is
  3308.  receiving the WM_PAINT message. Although this is a somewhat unusual
  3309.  application of the technique, storing window-specific data in the reserved
  3310.  area is often quite handy when two or more windows share the same window
  3311.  procedure.
  3312.  
  3313.  The processing of the WM_CLOSE message in ChildWndProc destroys the window
  3314.  being closed but doesn't terminate the program:
  3315.  
  3316.    case WM_CLOSE:
  3317.         WinDestroyWindow (WinQueryWindow (hwnd, QW_PARENT, FALSE)) ;
  3318.         return 0 ;
  3319.  
  3320.  WinQueryWindow obtains the parent of the client window (which is its frame
  3321.  window); destroying that frame window also destroys the client window.
  3322.  
  3323.  The program can be terminated only from the main window. After leaving the
  3324.  message loop, WELCOME3 destroys the program's top-level frame window as
  3325.  usual:
  3326.  
  3327.    WinDestroyWindow (hwndFrame) ;
  3328.  
  3329.  If one or both of the two child standard windows still exist, they, too,
  3330.  will be destroyed as a result. All windows in WELCOME3 are descendants of
  3331.  hwndFrame, so the one WinDestroyWindow call destroys all the windows in the
  3332.  program.
  3333.  
  3334.  
  3335.  Controls and Their Owners
  3336.  
  3337.  The frame window is the parent of all other windows created in the
  3338.  WinCreateStdWindow function. The frame window is also the "owner" of these
  3339.  other windows. A window is always displayed to the foreground of its owner
  3340.  (if it has one). However, it is not clipped to the surface of its owner. As
  3341.  with the parent/child relationship, when a window is hidden, minimized, or
  3342.  destroyed, the windows it owns are also hidden, minimized, or destroyed.
  3343.  
  3344.  The owner relationship also affects how messages are sent between the
  3345.  windows. The title bar, system menu, and minimize/maximize windows are often
  3346.  called "control windows." Control windows usually have a relatively simple
  3347.  appearance and function. The primary job of a control window is to receive
  3348.  user input (keystrokes and mouse activity) in the form of messages and then
  3349.  send notification messages to the window's owner. The owner of the control
  3350.  window (which in all the examples so far is a frame window) then acts on the
  3351.  notification message.
  3352.  
  3353.  For example, when you click on the maximize icon with the mouse, the
  3354.  minimize/maximize window sends a WM_SYSCOMMAND message to its owner──the
  3355.  frame window. The frame window then begins the process of maximizing the
  3356.  window. Likewise, the title bar window notifies the frame window of a new
  3357.  window position.
  3358.  
  3359.  Although every window (except object windows and the desktop window itself)
  3360.  has a parent, windows do not need owners. The frame window created in
  3361.  WinCreateStdWindow has no owner. The frame window is the owner of the client
  3362.  window, but the client window doesn't really need an owner either.
  3363.  
  3364.  You can determine the owner of a window by calling WinQueryWindow:
  3365.  
  3366.    hwndOwner = WinQueryWindow (hwnd, QW_OWNER, FALSE) ;
  3367.  
  3368.  A window can be assigned a new owner:
  3369.  
  3370.    WinSetOwner (hwnd, hwndNewOwner) ;
  3371.  
  3372.  The hwndNewOwner parameter can be set to NULL. This causes the window whose
  3373.  handle is hwnd to have no owner.
  3374.  
  3375.  
  3376.  Registering the Window Class
  3377.  
  3378.  Let's back up a little and examine in more detail some of the functions
  3379.  involved in creating a standard window. The standard window usually includes
  3380.  a client window. A preliminary step in creating a client window is the
  3381.  registering of a class for that window. The call to WinRegisterClass in
  3382.  WELCOME1.C from Chapter 2 looks like this:
  3383.  
  3384.    WinRegisterClass (
  3385.                   hab,                // Anchor block handle
  3386.                   szClientClass,      // Name of class being registered
  3387.                   ClientWndProc,      // Window procedure for class
  3388.                   CS_SIZEREDRAW,      // Class style
  3389.                   0;                  // Extra bytes to reserve
  3390.  
  3391.  Of these five parameters, the second and third are the most important. The
  3392.  second parameter is the name of the window class being registered. The name
  3393.  is a zero-terminated character string generally derived from the name of the
  3394.  program. In WELCOME1 the class name is "Welcome1." The third parameter is
  3395.  the address of the window procedure for the class. This window procedure
  3396.  processes all messages to all windows that are later created based on this
  3397.  class.
  3398.  
  3399.  The class style parameter is a 32-bit unsigned long integer that sets
  3400.  certain characteristics of all windows later created based on the class. You
  3401.  can set the class style parameter to 0L for a default class style. Or you
  3402.  can use one or more identifiers beginning with the letters CS ("class
  3403.  style") defined in PMWIN.H to specify a nondefault class style. You combine
  3404.  these identifiers with the C bitwise OR operator (|). Each identifier sets
  3405.  one bit in the class style. For this reason, the identifiers are sometimes
  3406.  called "class style bits." Ten class style bits are defined in PMWIN.H and
  3407.  are shown in Figure 3-8 in a diagram that indicates how each identifier
  3408.  contributes to the resultant 32-bit window style.
  3409.  
  3410.  These class styles are described in the documentation that accompanies the
  3411.  Microsoft OS/2 Programmer's Toolkit. Most of them are not commonly used. For
  3412.  the programs in this book, I use only CS_SIZEREDRAW and CS_SIZEREPAINT. The
  3413.  CS_SIZEREDRAW bit affects how the Presentation Manager should invalidate a
  3414.  window (and hence cause the window to receive a WM_PAINT message) when it is
  3415.  resized by the user. If the CS_SIZEREDRAW bit is not set and the window is
  3416.  reduced in size, the Presentation Manager does not need to invalidate the
  3417.  window. The part of the window outside the new size can be simply erased.
  3418.  When the CS_SIZEREDRAW bit is set, the entire window is invalidated when it
  3419.  is resized. CS_SIZEREDRAW should be used for all windows whose appearance
  3420.  depends on the size of the window. Because we have been displaying centered
  3421.  text in our client windows, CS_SIZEREDRAW is proper for the window class.
  3422.  
  3423.  Figure 3-8.  The window class style bits.
  3424.  
  3425.     ┌──┬──┬──┬──┬──┬──┬──┬───┬──┬──┬──┬──┬──┬──┐
  3426.     │31│30│29│28│27│26│25│...│ 5│ 4│ 3│ 2│ 1│ 0│
  3427.     └──┴──┴─┬┴─┬┴─┬┴─┬┴─┬┴───┴─┬┴─┬┴─┬┴─┬┴──┴─┬┘
  3428.             │  │  │  │  │      │  │  │  │     │
  3429.             │  │  │  │  │      │  │  │  │     └ CS_MOVENOTIFY
  3430.             │  │  │  │  │      │  │  │  └────── CS_SIZEREDRAW
  3431.             │  │  │  │  │      │  │  └───────── CS_HITTEST
  3432.             │  │  │  │  │      │  └──────────── CS_PUBLIC
  3433.             │  │  │  │  │      └─────────────── CS_FRAME
  3434.             │  │  │  │  └────────────────────── CS_SYNCPAINT
  3435.             │  │  │  └───────────────────────── CS_SAVEBITS
  3436.             │  │  └──────────────────────────── CS_PARENTCLIP
  3437.             │  └─────────────────────────────── CS_CLIPSIBLINGS
  3438.             └────────────────────────────────── CS_CLIPCHILDREN
  3439.  
  3440.  
  3441.  When CS_SYNCPAINT is set, WM_PAINT messages are sent directly to a window
  3442.  procedure when part of the window becomes invalid. When this bit is not set,
  3443.  WM_PAINT messages are posted to the message queue and retrieved later. The
  3444.  CS_SYNCPAINT bit is used mostly with small control windows that must be
  3445.  repainted immediately.
  3446.  
  3447.  The parameter to WinRegisterClass labeled "extra bytes to reserve" reserves
  3448.  a block of memory associated with each window created based on this class.
  3449.  You put data into this area using WinSetWindowUShort, WinSetWindowULong, and
  3450.  WinSetWindowPtr; you retrieve it by using WinQueryWindowUShort,
  3451.  WinQueryWindowULong, and WinQueryWindowPtr. You can do whatever you want
  3452.  with this memory. As you saw in WELCOME3, it's a handy place to store data
  3453.  unique to each window. Here's the general rule: When a variable defined in a
  3454.  window procedure is needed only during the processing of a message, use an
  3455.  automatic variable. To retain information from message to message, use
  3456.  static variables. However, if two or more windows share the same window
  3457.  procedure, use static variables only for data that can be shared among all
  3458.  windows. Use the reserved area for data unique to each window.
  3459.  
  3460.  
  3461.  Creating the Standard Window
  3462.  
  3463.  The WinCreateStdWindow call from last chapter's WELCOME1 program looks like
  3464.  this:
  3465.  
  3466.    hwndFrame = WinCreateStdWindow (
  3467.                   HWND_DESKTOP,       // Parent window handle
  3468.                   WS_VISIBLE          // Style of frame window
  3469.                   &flFrameFlags,      // Pointer to control data
  3470.                   szClientClass,      // Client window class name
  3471.                   NULL,               // Title bar text
  3472.                   0L,                 // Style of client window
  3473.                   NULL,               // Module handle for resources
  3474.                   0,                  // ID of resources
  3475.                   &hwndClient) ;      // Pointer to client window handle
  3476.  
  3477.  Two parameters in the WinCreateStdWindow function are "window styles": The
  3478.  second parameter is the window style of the frame window, and the sixth
  3479.  parameter is the window style of the client window. A window style is a
  3480.  32-bit unsigned long integer. Like the class style discussed previously, the
  3481.  window style sets certain characteristics of the window. But although the
  3482.  class style applies to all windows based on the class, the window style
  3483.  applies only to the particular window being created.
  3484.  
  3485.  The PMWIN.H header file contains identifiers (sometimes called "window style
  3486.  bits") to set bits in the window style when the identifiers are combined
  3487.  with the C bitwise OR operator (|).
  3488.  
  3489.  The high 16 bits of the window style are defined in the same way for all
  3490.  window classes. The identifiers begin with WS ("window style"). These are
  3491.  shown in Figure 3-9.
  3492.  
  3493.  Like the control style flags, many of these are rather rare. The
  3494.  WS_SYNCPAINT, WS_SAVEBITS, WS_PARENTCLIP, WS_CLIPSIBLINGS, and
  3495.  WS_CLIPCHILDREN bits have the same purpose as the equivalent class style
  3496.  bits. Thus you can create a window class without these styles but then
  3497.  create windows based on that class that use these styles. The only window
  3498.  style from Figure 3-9 that we've used so far for the frame window is the
  3499.  WS_VISIBLE bit. By default, a window is invisible when it's created.
  3500.  Specifying WS_VISIBLE overrides that default. Alternatively, you can exclude
  3501.  WS_VISIBLE from the frame window style when creating the window and later
  3502.  call WinSetWindowPos and WinShowWindow. The WS_VISIBLE bit isn't required
  3503.  for the client window style, because the Presentation Manager specifically
  3504.  makes the client window visible.
  3505.  
  3506.  The WS_GROUP and WS_TABSTOP style bits are used only for control windows
  3507.  (such as buttons) within dialog boxes. (Chapter 14 is devoted to dialog
  3508.  boxes.)
  3509.  
  3510.  Figure 3-9.  The high window style bits.
  3511.  
  3512.     ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
  3513.     │31│30│29│28│27│26│25│24│23│22│21│20│19│18│17│16│
  3514.     └─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴──┴──┴──┴──┴──┴─┬┴─┬┘
  3515.       │  │  │  │  │  │  │  │  │                 │  └─ WS_GROUP
  3516.       │  │  │  │  │  │  │  │  │                 └──── WS_TABSTOP
  3517.       │  │  │  │  │  │  │  │  └────────────────────── WS_MAXIMIZED
  3518.       │  │  │  │  │  │  │  └───────────────────────── WS_MINIMIZED
  3519.       │  │  │  │  │  │  └──────────────────────────── WS_SYNCPAINT
  3520.       │  │  │  │  │  └─────────────────────────────── WS_SAVEBITS
  3521.       │  │  │  │  └────────────────────────────────── WS_PARENTCLIP
  3522.       │  │  │  └───────────────────────────────────── WS_CLIPSIBLINGS
  3523.       │  │  └──────────────────────────────────────── WS_CLIPCHILDREN
  3524.       │  └─────────────────────────────────────────── WS_DISABLED
  3525.       └────────────────────────────────────────────── WS_VISIBLE
  3526.  
  3527.  
  3528.  The WS_MAXIMIZED bit causes a window to be maximized when the window is
  3529.  first displayed. Similarly, the WS_MINIMIZED bit causes the window to be
  3530.  initially displayed as an icon.
  3531.  
  3532.  If the WS_DISABLED bit is set, the window can't receive mouse input and is
  3533.  generally inert. The window can be subsequently enabled by a call to
  3534.  WinEnableWindow.
  3535.  
  3536.  The low 16 bits of the window style have different meanings, depending on
  3537.  the window class. The window procedure for the class interprets these bits.
  3538.  We'll see examples of this in the WELCOME4 program coming up shortly.
  3539.  
  3540.  The Frame Creation Flags
  3541.  
  3542.  The third parameter to WinCreateStdWindow is a pointer to a ULONG that
  3543.  indicates what child windows should be created in the standard window. The
  3544.  frame creation flags you use for this are shown in Figure 3-10. The PMWIN.H
  3545.  header file also defines FCF_STANDARD to be the same as
  3546.  
  3547.    FCF_TITLEBAR | FCF_SYSMENU | FCF_MENU |
  3548.    FCF_SIZEBORDER | FCF_MINMAX | FCF_ICON |
  3549.    FCF_ACCELTABLE | FCF_SHELLPOSITION | FCF_TASKLIST
  3550.  
  3551.  The FCF_MINMAX identifier is the same as
  3552.  
  3553.    FCF_MINBUTTON | FCF_MAXBUTTON
  3554.  
  3555.  You can experiment with the WELCOME1, WELCOME2, or WELCOME3 program (within
  3556.  limits) by removing some of the frame creation flags and putting in others.
  3557.  For example, you can exclude FCF_SYSMENU by using
  3558.  
  3559.    flFrame Flags = FCF_TITLEBAR | FCF_SIZEBORDER |
  3560.                    FCF_MINMAX | FCF_SHELLPOSITION |
  3561.                    FCF_TASKLIST ;
  3562.  
  3563.  In this case, the system menu window isn't created, and the title bar
  3564.  extends to the left to fill the space. You'll have to exit the program from
  3565.  the Task Manager. If you exclude FCF_MINMAX, then the minimize/maximize
  3566.  window isn't created, and the title bar again fills the space. The Minimize
  3567.  and Maximize options are also disabled on the system menu. You can use
  3568.  FCF_MINBUTTON or FCF_MAXBUTTON to include one option but not the other.
  3569.  
  3570.  If you exclude FCF_TITLEBAR, the title bar isn't created, and the
  3571.  Presentation Manager ignores the "title bar text" parameter of
  3572.  WinCreateStdWindow. The system menu and minimize/maximize box are created
  3573.  
  3574.  Figure 3-10.  The frame creation flag bits.
  3575.  
  3576.  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
  3577.  │19│18│17│16│15│14│13│12│11│10│ 9│ 8│ 7│ 6│ 5│ 4│ 3│ 2│ 1│ 0│
  3578.  └──┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┘
  3579.       │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  └ FCF_TITLE
  3580.       │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  └─── FCF_SYSMENU
  3581.       │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  └────── FCF_MENU
  3582.       │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  └───────── FCF_SIZEBORDER
  3583.       │  │  │  │  │  │  │  │  │  │  │  │  │  │  └──────────── FCF_MINBUTTON
  3584.       │  │  │  │  │  │  │  │  │  │  │  │  │  └─────────────── FCF_MAXBUTTON
  3585.       │  │  │  │  │  │  │  │  │  │  │  │  └────────────────── FCF_VERTSCROLL
  3586.       │  │  │  │  │  │  │  │  │  │  │  └───────────────────── FCF_HORZSCROLL
  3587.       │  │  │  │  │  │  │  │  │  │  └──────────────────────── FCF_DLGBORDER
  3588.       │  │  │  │  │  │  │  │  │  └─────────────────────────── FCF_BORDER
  3589.       │  │  │  │  │  │  │  │  └────────────────────── FCF_SHELLPOSITION
  3590.       │  │  │  │  │  │  │  └───────────────────────── FCF_TASKLIST
  3591.       │  │  │  │  │  │  └──────────────────────────── FCF_NOBYTEALIGN
  3592.       │  │  │  │  │  └─────────────────────────────── FCF_NOMOVEWITHOWNER
  3593.       │  │  │  │  └────────────────────────────────── FCF_ICON
  3594.       │  │  │  └───────────────────────────────────── FCF_ACCELTABLE
  3595.       │  │  └──────────────────────────────────────── FCF_SYSMODAL
  3596.       │  └─────────────────────────────────────────── FCF_SCREENALIGN
  3597.       └────────────────────────────────────────────── FCF_MOUSEALIGN
  3598.  
  3599.  
  3600.  FCF_SYSMENU and FCF_MINMAX are specified) and displayed in the normal
  3601.  places. But the area normally occupied by the title bar is not part of the
  3602.  client window. You can't move the window, because that is a function of the
  3603.  title bar.
  3604.  
  3605.  If you exclude FCF_SIZEBORDER, the sizing border window isn't created.
  3606.  Without the sizing border, the window not only looks a little naked, but the
  3607.  user can change the size of the window only by minimizing or maximizing it
  3608.  from the system menu or from the minimize/maximize box. You'll probably want
  3609.  to use FCF_BORDER to draw a thin black border around the naked window. If
  3610.  you use both FCF_SIZEBORDER and FCF_BORDER, FCF_BORDER is ignored.
  3611.  
  3612.  The FCF_DLGBORDER frame creation flag bit causes a wide border to be drawn.
  3613.  This is more commonly seen on dialog boxes. Like the title bar, the dialog
  3614.  border uses color to indicate if the window is active. If you use both
  3615.  FCF_DLGBORDER and FCF_SIZEBORDER for the window, FCF_DLGBORDER is ignored.
  3616.  
  3617.  You can include FCF_VERTSCROLL or FCF_HORZSCROLL or both in the frame
  3618.  creation flags. The window will then include scroll bars. The vertical
  3619.  scroll bar appears to the right of the client window, and the horizontal
  3620.  scroll bar is on the bottom. We'll start using scroll bars in the next
  3621.  chapter.
  3622.  
  3623.  At the moment you can't use the FCF_MENU, FCF_ICON, or FCF_ACCELTABLE bits
  3624.  in the frame creation flags. These bits cause the Presentation Manager to
  3625.  attempt to load a menu, icon, or keyboard accelerator table from the module
  3626.  (a .EXE or .DLL file) whose module handle is indicated in the seventh
  3627.  parameter of the WinCreateStdWindow function. Menus, icons, and accelerator
  3628.  tables are known as "resources." Every resource has an ID number. The ID
  3629.  number for all three of these resources must be the same and is specified as
  3630.  the eighth parameter in WinCreateStdWindow.
  3631.  
  3632.  Note that some frame creation flags──specifically the FCF_TITLEBAR,
  3633.  FCF_SYSMENU, FCF_MENU, FCF_MINBUTTON, FCF_MAXBUTTON, FCF_VERTSCROLL, and
  3634.  FCF_HORISCROLL flags──cause windows to be created; others (such as
  3635.  FCF_SIZEBORDER, FCF_BORDER, and FCF_DLGBORDER) affect only the appearance
  3636.  and functionality of the frame window.
  3637.  
  3638.  
  3639.  The WinCreateWindow Function
  3640.  
  3641.  The WinCreateStdWindow function creates several windows organized around a
  3642.  frame window. Within the Presentation Manager, each window is created by a
  3643.  call to WinCreateWindow. This function is available for use by your programs
  3644.  also. It looks like this:
  3645.  
  3646.    hwnd = WinCreateWindow (
  3647.                   hwndParent,         // Parent window handle
  3648.                   szClassName,        // Window class
  3649.                   szText,             // Window text
  3650.                   WS_...,             // Window style
  3651.                   xStart, yStart,     // Initial position of window
  3652.                   xSize, ySize,       // Initial size of window
  3653.                   hwndOwner,          // Owner window handle
  3654.                   hwndOrder,          // Placement window handle
  3655.                   idChild,            // Child window ID
  3656.                   pControlData,       // Control data
  3657.                   pPresParams) ;      // Presentation parameters
  3658.  
  3659.  The parameters to this function indicate the full array of information
  3660.  required to create a window, and they show how WinCreateStdWindow makes the
  3661.  job of creating a standard window in your program a whole lot simpler.
  3662.  
  3663.  You'll note here that each window has a "window text." But many control
  3664.  windows (such as the system menu window, sizing border window, and
  3665.  minimize/maximize window) don't display this text. The Presentation Manager
  3666.  uses the "title bar text" parameter to WinCreateStdWindow (concatenated to
  3667.  the name under which the program was started) as the "window text" parameter
  3668.  to WinCreateWindow only when it is creating the title bar window. The title
  3669.  bar window procedure displays that text in its window.
  3670.  
  3671.  Each window also has a position and size. The position is relative to the
  3672.  lower-left corner of the window's parent. We haven't been worrying about
  3673.  this. The Presentation Manager gives the frame window a default position and
  3674.  size and then organizes the other windows within that.
  3675.  
  3676.  The Predefined Window Classes
  3677.  
  3678.  In the WinCreateStdWindow call, only one window class parameter is required
  3679.  ──the window class of the client window. However, the Presentation Manager
  3680.  needs to specify a window class in each WinCreateWindow call it makes when
  3681.  creating the standard window. For the windows other than the client window,
  3682.  the Presentation Manager uses predefined window classes. These have
  3683.  identifiers in PMWIN.H and are shown in the following table:
  3684.  
  3685.     Predefined          Type of Window
  3686.     Window Class
  3687.     WC_FRAME            Standard frame window (including dialog boxes)
  3688.     WC_BUTTON           Push button, check box, and so on
  3689.     WC_MENU             Menu (including system menu & minimize/maximize
  3690.                         window)
  3691.     WC_STATIC           Text field, static rectangle
  3692.     WC_ENTRYFIELD       Text editing field
  3693.     WC_LISTBOX          List box
  3694.     WC_SCROLLBAR        Scroll bar
  3695.     WC_TITLEBAR         Standard title bar
  3696.  
  3697.  Each of these window classes has a corresponding window procedure in
  3698.  PMWIN.DLL.
  3699.  
  3700.  In the WinCreateStdWindow calls made in the various WELCOME programs, the
  3701.  Presentation Manager creates windows based on the WC_FRAME, WC_MENU, and
  3702.  WC_TITLEBAR styles. Perhaps it will be instructive to call WinCreateWindow
  3703.  ourselves in a program and see how this works.
  3704.  
  3705.  Creating Child Control Windows
  3706.  
  3707.  The WELCOME4 program, shown in Figure 3-11, creates one standard window and
  3708.  three control windows as children of the client window. These three control
  3709.  windows are created using WinCreateWindow and are based on the predefined
  3710.  window classes of WC_BUTTON, WC_SCROLLBAR, and WC_ENTRYFIELD.
  3711.  
  3712.  Figure 3-11.  The WELCOME4 program.
  3713.  
  3714.    The WELCOME4 File
  3715.  
  3716.    #--------------------
  3717.    # WELCOME4 make file
  3718.    #--------------------
  3719.  
  3720.    welcome4.obj : welcome4.c
  3721.         cl -c -G2sw -W3 welcome4.c
  3722.  
  3723.    welcome4.exe : welcome4.obj welcome4.def
  3724.         link welcome4, /align:16, NUL, os2, welcome4
  3725.  
  3726.    The WELCOME4.C File
  3727.  
  3728.    /*-------------------------------------------------------------
  3729.       WELCOME4.C -- Creates a Top-Level Window and Three Children
  3730.      -------------------------------------------------------------*/
  3731.  
  3732.    #define INCL_WIN
  3733.    #include <os2.h>
  3734.  
  3735.    #define ID_BUTTON 1
  3736.    #define ID_SCROLL 2
  3737.    #define ID_ENTRY  3
  3738.  
  3739.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  3740.  
  3741.    int main (void)
  3742.         {
  3743.         static CHAR  szClientClass [] = "Welcome4" ;
  3744.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU   |
  3745.                                     FCF_BORDER        | FCF_MINBUTTON |
  3746.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  3747.         HAB          hab ;
  3748.         HMQ          hmq ;
  3749.         HWND         hwndFrame,  hwndClient ;
  3750.         QMSG         qmsg ;
  3751.         RECTL        rcl ;
  3752.  
  3753.         hab = WinInitialize (0) ;
  3754.         hmq = WinCreateMsgQueue (hab, 0) ;
  3755.  
  3756.         WinRegisterClass (
  3757.                        hab,                // Anchor block handle
  3758.                        szClientClass,      // Name of class being registered
  3759.                        ClientWndProc,      // Window procedure for class
  3760.                        CS_SIZEREDRAW,      // Class style
  3761.                        0) ;                // Extra bytes to reserve
  3762.  
  3763.         hwndFrame = WinCreateStdWindow (
  3764.                        HWND_DESKTOP,       // Parent window handle
  3765.                        WS_VISIBLE,         // Style of frame window
  3766.                        &flFrameFlags,      // Pointer to control data
  3767.                        szClientClass,      // Client window class name
  3768.                        NULL,               // Title bar text
  3769.                        0L,                 // Style of client window
  3770.                        NULL,               // Module handle for resources
  3771.                        0,                  // ID of resources
  3772.                        &hwndClient) ;      // Pointer to client window handle
  3773.  
  3774.         WinSendMsg (hwndFrame, WM_SETICON,
  3775.                          WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE
  3776.                          NULL) ;
  3777.  
  3778.              /*--------------------------------------------------------
  3779.                 Find dimensions of client window for sizes of children
  3780.                --------------------------------------------------------*/
  3781.  
  3782.         WinQueryWindowRect (hwndClient, &rcl) ;
  3783.         rcl.xRight /= 3 ;                            // Divide width in thirds
  3784.  
  3785.              /*---------------------------
  3786.                 Create push button window
  3787.                ---------------------------*/
  3788.  
  3789.         WinCreateWindow (
  3790.                        hwndClient,                   // Parent window handle
  3791.                        WC_BUTTON,                    // Window class
  3792.                        "Big Button",                 // Window text
  3793.                        WS_VISIBLE                    // Window style
  3794.                             | BS_PUSHBUTTON,
  3795.                        10,                           // Window position
  3796.  
  3797.                        10,
  3798.                        (SHORT) rcl.xRight - 20,      // Window size
  3799.                        (SHORT) rcl.yTop - 20,
  3800.                        hwndClient,                   // Owner window handle
  3801.                        HWND_BOTTOM,                  // Placement window handl
  3802.                        ID_BUTTON,                    // Child window ID
  3803.                        NULL,                         // Control data
  3804.                        NULL) ;                       // Presentation parameter
  3805.  
  3806.              /*--------------------------
  3807.                 Create scroll bar window
  3808.                --------------------------*/
  3809.  
  3810.         WinCreateWindow (
  3811.                        hwndClient,                   // Parent window handle
  3812.                        WC_SCROLLBAR,                 // Window class
  3813.                        NULL,                         // Window text
  3814.                        WS_VISIBLE                    // Window style
  3815.                             | SBS_VERT,
  3816.                        (SHORT) rcl.xRight + 10,      // Window position
  3817.                        10,
  3818.                        (SHORT) rcl.xRight - 20,      // Window size
  3819.                        (SHORT) rcl.yTop - 20,
  3820.                        hwndClient,                   // Owner window handle
  3821.                        HWND_BOTTOM,                  // Placement window handl
  3822.                        ID_SCROLL,                    // Child window ID
  3823.                        NULL,                         // Control data
  3824.                        NULL) ;                       // Presentation parameter
  3825.  
  3826.              /*---------------------------
  3827.                 Create entry field window
  3828.                ---------------------------*/
  3829.  
  3830.         WinCreateWindow (
  3831.                        hwndClient,                   // Parent window handle
  3832.                        WC_ENTRYFIELD,                // Window class
  3833.                        NULL,                         // Window text
  3834.                        WS_VISIBLE                    // Window style
  3835.                             | ES_MARGIN
  3836.                             | ES_AUTOSCROLL,
  3837.                        2 * (SHORT) rcl.xRight + 10,  // Window position
  3838.                        10,
  3839.                        (SHORT) rcl.xRight - 20,      // Window size
  3840.                        (SHORT) rcl.yTop - 20,
  3841.                        hwndClient,                   // Owner window handle
  3842.                        HWND_BOTTOM,                  // Placement window handl
  3843.  
  3844.                        ID_ENTRY,                     // Child window ID
  3845.                        NULL,                         // Control data
  3846.                        NULL) ;                       // Presentation parameter
  3847.  
  3848.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  3849.              WinDispatchMsg (hab, &qmsg) ;
  3850.  
  3851.         WinDestroyWindow (hwndFrame) ;
  3852.         WinDestroyMsgQueue (hmq) ;
  3853.         WinTerminate (hab) ;
  3854.         return 0 ;
  3855.         }
  3856.  
  3857.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  3858.         {
  3859.         switch (msg)
  3860.              {
  3861.              case WM_COMMAND:
  3862.                   switch (COMMANDMSG(&msg)->cmd)
  3863.                        {
  3864.                        case ID_BUTTON:
  3865.                             WinAlarm (HWND_DESKTOP, WA_NOTE) ;
  3866.                             return 0 ;
  3867.                        }
  3868.                   break ;
  3869.  
  3870.              case WM_ERASEBACKGROUND:
  3871.                   return 1 ;
  3872.              }
  3873.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  3874.         }
  3875.  
  3876.    The WELCOME4.DEF File
  3877.  
  3878.    ;-------------------------------------
  3879.    ; WELCOME4.DEF module definition file
  3880.    ;-------------------------------------
  3881.  
  3882.    NAME           WELCOME4  WINDOWAPI
  3883.  
  3884.    DESCRIPTION    'Creates Top-Level and 3 Children (C) Charles Petzold, 1988'
  3885.    PROTMODE
  3886.    HEAPSIZE       1024
  3887.    STACKSIZE      8192
  3888.    EXPORTS        ClientWndProc
  3889.  
  3890.  
  3891.  The standard window in WELCOME4 uses FCF_BORDER rather than FCF_SIZEBORDER
  3892.  and has no maximize icon. The window is shown in Figure 3-12.
  3893.  
  3894.  After creating the standard window, WELCOME4 makes a call to
  3895.  WinQueryWindowRect to obtain the rectangle structure that defines the size
  3896.  of the client window:
  3897.  
  3898.    WinQueryWindowRect (hwndClient, &rcl) ;
  3899.  
  3900.  The xRight field of this structure is the width of the client area in
  3901.  pixels. WELCOME4 divides it by 3 to be used in the three WinCreateWindow
  3902.  calls.
  3903.  
  3904.  The first WinCreateWindow call creates the window based on the WC_BUTTON
  3905.  class:
  3906.  
  3907.    WinCreateWindow (
  3908.                   hwndClient,                   // Parent window handle
  3909.                   WC_BUTTON,                    // Window class
  3910.                   "Big Button",                 // Window text
  3911.                   WS_VISIBLE                    // Window style
  3912.                        | BS_PUSHBUTTON,
  3913.                   10,                           // Window position
  3914.                   10,
  3915.                   (SHORT) rcl.xRight - 20,      // Initial size of window
  3916.                   (SHORT) rcl.yTop - 20,
  3917.                   hwndClient,                   // Owner window handle
  3918.                   HWND_BOTTOM,                  // Placement window handle
  3919.                   ID_BUTTON,                    // Child window ID
  3920.                   NULL,                         // Control data
  3921.                   NULL) ;                       // Presentation parameters
  3922.  
  3923.  This call creates a large push button (such as those that appear in dialog
  3924.  boxes) in the left third of the client window. The text inside the button is
  3925.  "Big Button." Both the parent and owner are set to the hwndClient window
  3926.  handle returned from the original WinCreateStdWindow call. The window style
  3927.  uses WS_VISIBLE and BS_PUSHBUTTON. Identifiers beginning with BS ("button
  3928.  style") are class-specific window styles for buttons. The initial position
  3929.  of the window is relative to the lower-left corner of the client window.
  3930.  These two parameters are both set to 10 pixels to provide a small margin
  3931.  around the push button. The size of the window is set to one-third the width
  3932.  of the client window and to the height of the client window, minus 20 pixels
  3933.  from each dimension.
  3934.  
  3935.  The second WinCreateWindow call creates a vertical scroll bar in the middle
  3936.  third of the client window:
  3937.  
  3938.    WinCreateWindow (
  3939.                   hwndClient,                   // Parent window handle
  3940.                   WC_SCROLLBAR,                 // Window class
  3941.                   NULL,                         // Window text
  3942.                   WS_VISIBLE                    // Window style
  3943.                        | SBS_VERT,
  3944.                   (SHORT) rcl.xRight + 10,      // Window position
  3945.                   10,
  3946.                   (SHORT) rcl.xRight - 20,      // Window size
  3947.                   (SHORT) rcl.yTop - 20,
  3948.                   hwndClient,                   // Owner window handle
  3949.                   HWND_BOTTOM,                  // Placement window handle
  3950.                   ID_SCROLL,                    // Child window ID
  3951.                   NULL,                         // Control data
  3952.                   NULL) ;                       // Presentation parameters
  3953.  
  3954.  The class is WC_SCROLLBAR, and the class-specific window style is SBS_VERT.
  3955.  EN>SBS stands for "scroll-bar style," and VERT indicates a vertical scroll
  3956.  bar.
  3957.  
  3958.  The third WinCreateWindow call creates a text entry field window:
  3959.  
  3960.    WinCreateWindow (
  3961.                   hwndClient,                   // Parent window handle
  3962.                   WC_ENTRYFIELD,                // Window class
  3963.                   NULL,                         // Window text
  3964.                   WS_VISIBLE,                   // Window style
  3965.                        | ES_MARGIN
  3966.                        | ES_AUTOSCROLL
  3967.                   2 * (SHORT) rcl.xRight + 10,  // Window position
  3968.                   10,
  3969.                   (SHORT) rcl.xRight - 20,      // Window size
  3970.                   (SHORT) rcl.yTop - 20,
  3971.                   hwndClient,                   // Owner window handle
  3972.                   HWND_BOTTOM,                  // Placement window handle
  3973.                   ID_ENTRY,                     // Child window ID
  3974.                   NULL,                         // Control data
  3975.                   NULL) ;                       // Presentation parameters
  3976.  
  3977.  The class is WC_ENTRYFIELD and the style bits are ES_MARGIN (to draw a
  3978.  border around the window) and ES_AUTOSCROLL (to scroll text horizontally
  3979.  within the window).
  3980.  
  3981.  All three WinCreateWindow calls return the handle to the window they create,
  3982.  but WELCOME4 doesn't save these handles.
  3983.  
  3984.  Although WELCOME4's button and scroll bar may appear to be somewhat
  3985.  grotesque, they are still functional. When you click on the button with the
  3986.  mouse, it flashes. When you click on various parts of the scroll bar, they,
  3987.  too, flash. (You can't move the scroll bar slider──that's a program's
  3988.  responsibility, as you'll see in the next chapter.) You can even click on
  3989.  the text entry field and type in some text.
  3990.  
  3991.  These three control windows created in WELCOME4 send "notification messages"
  3992.  to their owner (which is the client window) when they receive user input.
  3993.  For example, the push button sends its owner a WM_COMMAND message when the
  3994.  button is clicked with the mouse. ClientWndProc receives this message and
  3995.  beeps by calling WinAlarm. Likewise, the control windows that make up the
  3996.  standard window notify their owner (the frame window) of user input. The
  3997.  WELCOME4 family tree is shown in Figure 3-13. This family tree shows the
  3998.  parent-child relationship; the owner-owned relationship is identical to
  3999.  this, except that the desktop window doesn't own the frame window.
  4000.  
  4001.  Figure 3-13.  The WELCOME4 family tree.
  4002.  
  4003.                          Desktop
  4004.                           Window
  4005.                             │
  4006.                           Frame
  4007.                           Window
  4008.                        (hwndFrame)
  4009.                 ┌───────────┼───────────┬────────────┐
  4010.                 │           │           │            │
  4011.                 │           │           │            │
  4012.               Title       Client      System      Minimize/
  4013.                bar        window       menu       maximize
  4014.               window  (hwndClient)   window       window
  4015.                             │
  4016.                             │
  4017.             ┌───────────────┼───────────────┐
  4018.             │               │               │
  4019.           Button        Scroll bar        Text
  4020.           window          window          entry
  4021.                                           window
  4022.  
  4023.  
  4024.  Child window IDs
  4025.  When the Presentation Manager (or your program) creates child windows using
  4026.  the WinCreateWindow function, each child is assigned a "child window ID"
  4027.  that is specified as the eleventh parameter to WinCreateWindow. In WELCOME4,
  4028.  these ID numbers are set to ID_BUTTON, ID_SCROLL, and ID_ENTRY, which are
  4029.  defined at the top of the program as 1, 2, and 3. The control window uses
  4030.  this ID to identify itself to its owner when it sends a notification
  4031.  message. For example, in the WM_COMMAND notification message that push
  4032.  buttons send, the mp1 parameter contains this ID. Thus a window can contain
  4033.  many push buttons or other control windows, each with a different ID. (We'll
  4034.  examine this in greater detail in Chapters 11, 13, and 14.)
  4035.  
  4036.  When the frame window creates its children, each of them is assigned an ID
  4037.  number. As shown in the following table, these are fixed values defined in
  4038.  PMWIN.H, and have identifiers beginning with the letters FID (which stands
  4039.  for "frame ID").
  4040.  
  4041.     FID                   Type of Child Window
  4042.     FID_SYSMENU           System menu
  4043.     FID_TITLEBAR          Title bar
  4044.     FID_MINMAX            Minimize/maximize box
  4045.     FID_MENU              Program's menu
  4046.     FID_VERTSCROLL        Vertical scroll bar
  4047.     FID_HORZSCROLL        Horizontal scroll bar
  4048.     FID_CLIENT            Client window
  4049.  
  4050.  A program can determine the window handle of a child window based on the
  4051.  parent window handle and the child ID:
  4052.  
  4053.    hwndChild = WinWindowFromID (hwndParent, idChild) ;
  4054.  
  4055.  If you need to know the window handle of the system menu window (for
  4056.  example), you can easily obtain it:
  4057.  
  4058.    hwndSysMenu = WinWindowFromID (hwndFrame, FID_SYSMENU) ;
  4059.  
  4060.  Why would you need this information? Well, you might want to send the system
  4061.  menu window a message. Improbable? Not at all──we'll do it in Chapter 13.
  4062.  
  4063.  You can also determine a window's ID from its window handle:
  4064.  
  4065.    idChild = WinQueryWindowUShort (hwnd, QWS_ID) ;
  4066.  
  4067.  The WinQueryWindowUShort, WinQueryWindowULong, and WinQueryWindowPtr
  4068.  functions also let you obtain a window's message queue handle, its style,
  4069.  and the address of the window procedure, as well as the reserved areas
  4070.  specified in the window class.
  4071.  
  4072.  Styles, Classes, and IDs
  4073.  
  4074.  By now you've seen similar identifiers connected with various parts of the
  4075.  standard window used in various ways. For the title bar, for example, you've
  4076.  seen identifiers named FCF_TITLEBAR, WC_TITLEBAR, and FID_TITLEBAR. This may
  4077.  all be a little confusing. Here's a table that can help you keep the
  4078.  identifiers straight.
  4079.  
  4080.     The Frame         Causes the Frame     With a Child Window
  4081.     Creation Flag:    Window to Create     ID Of:
  4082.                       a Child of Class:
  4083.     FCF_TITLEBAR      WC_TITLEBAR          FID_TITLEBAR
  4084.     FCF_SYSMENU       WC_MENU              FID_SYSMENU
  4085.     FCF_MENU          WC_MENU              FID_MENU
  4086.     FCF_MINMAX        WC_MENU              FID_MINMAX
  4087.     FCF_VERTSCROLL    WC_SCROLLBAR         FID_VERTSCROLL
  4088.     FCF_HORZSCROLL    WC_SCROLLBAR         FID_HORZSCROLL
  4089.  
  4090.  The FCF identifiers are used in the WinCreateStdWindow call to specify the
  4091.  window style of the frame window. Within the Presentation Manager, a call to
  4092.  the WinCreateWindow function creates each of the control windows. The window
  4093.  class is one of the WC identifiers, and the child window ID is an FID
  4094.  identifier.
  4095.  
  4096.  
  4097.  SECTION TWO       PAINTING THE CLIENT WINDOW
  4098.  
  4099.  Chapter 4  An Exercise in Text Output
  4100.  ───────────────────────────────────────────────────────────────────────────
  4101.  
  4102.  
  4103.  The Presentation Manager is a graphical environment, and yet for many
  4104.  applications the display of text and numbers is more important than
  4105.  pictures. Although it might be nice to write a database program that can
  4106.  include bitmapped images of employees' faces, the fact remains that the
  4107.  employees' names, addresses, and social security numbers are still the most
  4108.  important data. This chapter covers the basic concepts involved with
  4109.  displaying plain-vanilla text in the client window. Although the chapter
  4110.  touches on keyboard and mouse input, these subjects are discussed in more
  4111.  depth in Chapters 8 and 9.
  4112.  
  4113.  When programming for the Presentation Manager, you don't use OS/2 kernel
  4114.  functions such as DosWrite and VioWrtTTY or C functions such as printf and
  4115.  puts to write text to the screen. Instead, you use functions provided by the
  4116.  Graphics Programming Interface (GPI) component of the Presentation Manager.
  4117.  (Exceptions do exist: Several high-level drawing functions such as
  4118.  WinDrawText aren't really part of GPI. Also, we'll see in Chapter 7 how you
  4119.  can use the VioWrtTTY function in Presentation Manager programs.) GPI
  4120.  functions begin with the prefix Gpi. Although this chapter covers only text
  4121.  output, many of the concepts examined here are applicable to graphics also.
  4122.  
  4123.  
  4124.  Displaying Text on the Client Window
  4125.  
  4126.  As a case study, let's write a Presentation Manager program that displays
  4127.  all of the information obtainable from the WinQuerySysValue function.
  4128.  
  4129.  You can use WinQuerySysValue in a program to obtain the height and width of
  4130.  the screen as well as 46 other interesting pieces of information, mostly
  4131.  concerning the sizes of various windows created by the Presentation Manager.
  4132.  The first parameter to the function is the identifier HWND_DESKTOP, and the
  4133.  second parameter is one of the identifiers defined in PMWIN.H with the
  4134.  letters SV ("system value"). For example, the following call returns the
  4135.  height of the title bar in pixels:
  4136.  
  4137.    WinQuerySysValue (HWND_DESKTOP, SV_CYTITLEBAR)
  4138.  
  4139.  Like many of the values that WinQuerySysValue returns, this value depends on
  4140.  the resolution of the video display on which the Presentation Manager is
  4141.  running. In later chapters we'll use WinQuerySysValue for various purposes.
  4142.  Here we merely want to look at all the values. We'll display this
  4143.  information in the client window. The 48 items will be displayed, one per
  4144.  line, in three columns: the SV identifier passed to WinQuerySysValue, a
  4145.  description of the item, and the value returned from the function. The first
  4146.  version of the program to display these values is called SYSVALS1 and is
  4147.  shown in Figure 4-1.
  4148.  
  4149.  Figure 4-1.  The SYSVALS1 program.
  4150.  
  4151.    The SYSVALS1 File
  4152.  
  4153.    #--------------------
  4154.    # SYSVALS1 make file
  4155.    #--------------------
  4156.  
  4157.    sysvals1.obj : sysvals1.c sysvals.h
  4158.         cl -c -G2sw -W3 sysvals1.c
  4159.  
  4160.    sysvals1.exe : sysvals1.obj sysvals1.def
  4161.         link sysvals1, /align:16, NUL, os2, sysvals1
  4162.  
  4163.    The SYSVALS.H File
  4164.  
  4165.    /*----------------------------------------------
  4166.       SYSVALS.H -- System values display structure
  4167.      ----------------------------------------------*/
  4168.  
  4169.    #define NUMLINES (sizeof sysvals / sizeof sysvals [0])
  4170.  
  4171.    struct
  4172.         {
  4173.         SHORT sIndex ;
  4174.         CHAR  *szIdentifier ;
  4175.         CHAR  *szDescription ;
  4176.         }
  4177.  
  4178.         sysvals [] =
  4179.         {
  4180.         SV_SWAPBUTTON,     "SV_SWAPBUTTON",     "Mouse buttons swapped flag",
  4181.         SV_DBLCLKTIME,     "SV_DBLCLKTIME",     "Mouse double click time in ms
  4182.         SV_CXDBLCLK,       "SV_CXDBLCLK",       "Mouse double click area width
  4183.         SV_CYDBLCLK,       "SV_CYDBLCLK",       "Mouse double click area heigh
  4184.         SV_CXSIZEBORDER,   "SV_CXSIZEBORDER",   "Sizing border width",
  4185.         SV_CYSIZEBORDER,   "SV_CYSIZEBORDER",   "Sizing border height",
  4186.         SV_ALARM,          "SV_ALARM",          "Alarm enabled flag",
  4187.         SV_CURSORRATE,     "SV_CURSORRATE",     "Cursor blink rate",
  4188.         SV_FIRSTSCROLLRATE,"SV_FIRSTSCROLLRATE","Scroll bar time until repeats
  4189.         SV_SCROLLRATE,     "SV_SCROLLRATE",     "Scroll bar scroll rate",
  4190.         SV_NUMBEREDLISTS,  "SV_NUMBEREDLISTS",  "Flag for numbering of lists",
  4191.         SV_WARNINGFREQ,    "SV_WARNINGFREQ",    "Alarm frequency for WA_WARNIN
  4192.         SV_NOTEFREQ,       "SV_NOTEFREQ",       "Alarm frequency for WA_NOTE",
  4193.         SV_ERRORFREQ,      "SV_ERRORFREQ",      "Alarm frequency for WA_ERROR"
  4194.         SV_WARNINGDURATION,"SV_WARNINGDURATION","Alarm duration for WA_WARNING
  4195.         SV_NOTEDURATION,   "SV_NOTEDURATION",   "Alarm duration for WA_NOTE",
  4196.         SV_ERRORDURATION,  "SV_ERRORDURATION",  "Alarm duration for WA_ERROR",
  4197.         SV_CXSCREEN,       "SV_CXSCREEN",       "Screen width in pixels",
  4198.         SV_CYSCREEN,       "SV_CYSCREEN",       "Screen height in pixels",
  4199.         SV_CXVSCROLL,      "SV_CXVSCROLL",      "Vertical scroll bar width",
  4200.         SV_CYHSCROLL,      "SV_CYHSCROLL",      "Horizontal scroll bar height"
  4201.         SV_CYVSCROLLARROW, "SV_CYVSCROLLARROW", "Vertical scroll arrow height"
  4202.         SV_CXHSCROLLARROW, "SV_CXHSCROLLARROW", "Horizontal scroll arrow width
  4203.         SV_CXBORDER,       "SV_CXBORDER",       "Border width",
  4204.         SV_CYBORDER,       "SV_CYBORDER",       "Border height",
  4205.         SV_CXDLGFRAME,     "SV_CXDLGFRAME",     "Dialog window frame width",
  4206.         SV_CYDLGFRAME,     "SV_CYDLGFRAME",     "Dialog window frame height",
  4207.         SV_CYTITLEBAR,     "SV_CYTITLEBAR",     "Title bar height",
  4208.         SV_CYVSLIDER,      "SV_CYVSLIDER",      "Vertical scroll slider height
  4209.         SV_CXHSLIDER,      "SV_CXHSLIDER",      "Horizontal scroll slider widt
  4210.         SV_CXMINMAXBUTTON, "SV_CXMINMAXBUTTON", "Minimize/Maximize button widt
  4211.         SV_CYMINMAXBUTTON, "SV_CYMINMAXBUTTON", "Minimize/Maximize button heig
  4212.         SV_CYMENU,         "SV_CYMENU",         "Menu bar height",
  4213.         SV_CXFULLSCREEN,   "SV_CXFULLSCREEN",   "Full screen client window wid
  4214.         SV_CYFULLSCREEN,   "SV_CYFULLSCREEN",   "Full screen client window hei
  4215.         SV_CXICON,         "SV_CXICON",         "Icon width",
  4216.         SV_CYICON,         "SV_CYICON",         "Icon height",
  4217.         SV_CXPOINTER,      "SV_CXPOINTER",      "Pointer width",
  4218.         SV_CYPOINTER,      "SV_CYPOINTER",      "Pointer height",
  4219.         SV_DEBUG,          "SV_DEBUG",          "Debug version flag",
  4220.         SV_CMOUSEBUTTONS,  "SV_CMOUSEBUTTONS",  "Number of mouse buttons",
  4221.         SV_POINTERLEVEL,   "SV_POINTERLEVEL",   "Pointer display count",
  4222.         SV_CURSORLEVEL,    "SV_CURSORLEVEL",    "Cursor display count",
  4223.  
  4224.         SV_TRACKRECTLEVEL, "SV_TRACKRECTLEVEL", "Tracking rectangle display co
  4225.         SV_CTIMERS,        "SV_CTIMERS",        "Number of available timers",
  4226.         SV_MOUSEPRESENT,   "SV_MOUSEPRESENT",   "Mouse present flag",
  4227.         SV_CXBYTEALIGN,    "SV_CXBYTEALIGN",    "Horizontal pixel alignment va
  4228.         SV_CYBYTEALIGN,    "SV_CYBYTEALIGN",    "Vertical pixel alignment valu
  4229.         } ;
  4230.  
  4231.    The SYSVALS1.C File
  4232.  
  4233.    /*---------------------------------------------------
  4234.       SYSVALS1.C -- System Values Display Program No. 1
  4235.      ---------------------------------------------------*/
  4236.  
  4237.    #define INCL_WIN
  4238.    #define INCL_GPI
  4239.    #include <os2.h>
  4240.    #include <stdlib.h>
  4241.    #include <string.h>
  4242.    #include "sysvals.h"
  4243.  
  4244.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  4245.  
  4246.    int main (void)
  4247.         {
  4248.         static CHAR  szClientClass [] = "SysVals1" ;
  4249.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  4250.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  4251.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  4252.         HAB          hab ;
  4253.         HMQ          hmq ;
  4254.         HWND         hwndFrame, hwndClient ;
  4255.         QMSG         qmsg ;
  4256.  
  4257.         hab = WinInitialize (0) ;
  4258.         hmq = WinCreateMsgQueue (hab, 0) ;
  4259.  
  4260.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  4261.  
  4262.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  4263.                                         &flFrameFlags, szClientClass, NULL,
  4264.                                         0L, NULL, 0, &hwndClient) ;
  4265.  
  4266.         WinSendMsg (hwndFrame, WM_SETICON,
  4267.                          WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE
  4268.                          NULL) ;
  4269.  
  4270.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  4271.              WinDispatchMsg (hab, &qmsg) ;
  4272.  
  4273.         WinDestroyWindow (hwndFrame) ;
  4274.         WinDestroyMsgQueue (hmq) ;
  4275.         WinTerminate (hab) ;
  4276.         return 0 ;
  4277.         }
  4278.  
  4279.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  4280.         {
  4281.         static SHORT cxChar, cxCaps, cyChar, cyDesc, cxClient, cyClient ;
  4282.         CHAR         szBuffer [10] ;
  4283.         FONTMETRICS  fm ;
  4284.         HPS          hps ;
  4285.         POINTL       ptl ;
  4286.         SHORT        sLine ;
  4287.  
  4288.         switch (msg)
  4289.              {
  4290.              case WM_CREATE:
  4291.                   hps = WinGetPS (hwnd) ;
  4292.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  4293.  
  4294.                   cxChar = (SHORT) fm.lAveCharWidth ;
  4295.                   cxCaps = (SHORT) fm.lEmInc ;
  4296.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  4297.                   cyDesc = (SHORT) fm.lMaxDescender ;
  4298.  
  4299.                   WinReleasePS (hps) ;
  4300.                   return 0 ;
  4301.  
  4302.              case WM_SIZE:
  4303.                   cxClient = SHORT1FROMMP (mp2) ;
  4304.                   cyClient = SHORT2FROMMP (mp2) ;
  4305.                   return 0 ;
  4306.  
  4307.              case WM_PAINT:
  4308.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  4309.                   GpiErase (hps) ;
  4310.  
  4311.                   for (sLine = 0 ; sLine < NUMLINES ; sLine++)
  4312.                        {
  4313.                        ptl.x = cxCaps ;
  4314.                        ptl.y = cyClient - cyChar * (sLine + 1) + cyDesc ;
  4315.  
  4316.                        GpiCharStringAt (hps, &ptl,
  4317.                                  (LONG) strlen (sysvals[sLine].szIdentifier),
  4318.                                  sysvals[sLine].szIdentifier) ;
  4319.  
  4320.                        ptl.x += 20 * cxCaps ;
  4321.                        GpiCharStringAt (hps, &ptl,
  4322.                                  (LONG) strlen (sysvals[sLine].szDescription),
  4323.                                  sysvals[sLine].szDescription) ;
  4324.  
  4325.                        ltoa (WinQuerySysValue (HWND_DESKTOP,
  4326.                                   sysvals[sLine].sIndex), szBuffer, 10) ;
  4327.  
  4328.                        ptl.x += 38 * cxChar ;
  4329.                        GpiCharStringAt (hps, &ptl, (LONG) strlen (szBuffer),
  4330.                                         szBuffer) ;
  4331.                        }
  4332.                   WinEndPaint (hps) ;
  4333.                   return 0 ;
  4334.              }
  4335.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  4336.         }
  4337.  
  4338.    The SYSVALS1.DEF File
  4339.  
  4340.    ;-------------------------------------
  4341.    ; SYSVALS1.DEF module definition file
  4342.    ;-------------------------------------
  4343.  
  4344.    NAME           SYSVALS1  WINDOWAPI
  4345.  
  4346.    DESCRIPTION    'System Values Display No. 1 (C) Charles Petzold, 1988'
  4347.    PROTMODE
  4348.    HEAPSIZE       1024
  4349.    STACKSIZE      8192
  4350.    EXPORTS        ClientWndProc
  4351.  
  4352.  
  4353.  The SYSVALS.H header file defines a structure named sysvals that contains
  4354.  all the system value identifiers and text descriptions that SYSVALS1 needs
  4355.  to obtain and display the information from WinQuerySysValue. The same
  4356.  SYSVALS.H file will be used in the subsequent versions of the program in
  4357.  this chapter. Notice that the SYSVALS1 make file recompiles the program
  4358.  whenever the SYSVALS1.C or SYSVALS.H file is altered.
  4359.  
  4360.  The definition of the INCL_WIN and INCL_GPI identifiers near the top of
  4361.  SYSVALS1.C is required in order to include sections of the OS/2 header files
  4362.  that are omitted by default.
  4363.  
  4364.  The SYSVALS1 window is shown in Figure 4-2. You might have already noticed
  4365.  that SYSVALS1 is seriously flawed. Never fear; we'll hammer away at it until
  4366.  we get it right. Despite its flaws, SYSVALS1 illustrates many of the basic
  4367.  concepts involved in displaying text on your client window.
  4368.  
  4369.  
  4370.  Device-independent Programming
  4371.  
  4372.  One primary purpose of the Presentation Manager is to provide a
  4373.  "device-independent" environment for your applications. This means that your
  4374.  programs should run without change or special drivers on any machine──and
  4375.  in particular, with any video display adapter──on which the Presentation
  4376.  Manager itself runs. Some programmers who have experience with Microsoft
  4377.  Windows are already aware of the deep and satisfying pleasure that results
  4378.  from seeing their programs run without change on everything from the IBM
  4379.  Color/Graphics Adapter (with 640 pixels horizontally by 200 scan lines
  4380.  vertically) to high-resolution video adapters of 1664 by 1200. In the years
  4381.  to come, programmers who write applications for the Presentation Manager can
  4382.  experience the same pleasure in seeing their programs run on video displays
  4383.  of even higher resolution.
  4384.  
  4385.  Because a display driver is one of the dynamic link libraries in the
  4386.  Presentation Manager, Presentation Manager applications don't require their
  4387.  own video display drivers. The application makes various GPI calls, the
  4388.  Presentation Manager calls the display driver dynamic link library, and the
  4389.  display driver handles the hardware screen output. Of course, you do your
  4390.  part by writing Presentation Manager programs that can easily adapt
  4391.  themselves to different environments. This involves one basic rule: Don't
  4392.  assume anything.
  4393.  
  4394.  But with the Presentation Manager, there's really no need for assump tions:
  4395.  All the information you need concerning the video display can be obtained
  4396.  through various Presentation Manager functions. For example,
  4397.  WinQuerySysValue can tell you the width and height of the video display in
  4398.  pixels. Just about the only guarantee you have is that the video display can
  4399.  accommodate at least 80 text characters across and 24 text lines down when
  4400.  you use the standard default "system font" (which I'll discuss shortly). Of
  4401.  course, this doesn't mean you necessarily have access to the entire screen.
  4402.  Normally, your application must share the display with other programs (which
  4403.  explains why the results of your program should be designed to be functional
  4404.  in both maximized and nonmaximized windows).
  4405.  
  4406.  Most Presentation Manager programs have a sizing border that lets the user
  4407.  change the size of your program's window. This has a profound consequence:
  4408.  Not only can you not make any assumptions about the size of your program's
  4409.  client window, but you can't even assume that the size will remain constant
  4410.  while your program is running. So the first job we'll tackle is how a
  4411.  program can determine the size of its client window.
  4412.  
  4413.  The Size of the Client Window
  4414.  
  4415.  The programs presented in Chapters 2 and 3 obtained the size of the client
  4416.  window by calling
  4417.  
  4418.    WinQueryWindowRect (hwnd, &rcl) ;
  4419.  
  4420.  The rcl variable is a structure of type RECTL with four fields──xLeft,
  4421.  yBottom, xRight, and yTop. The WinQueryWindowRect function fills in these
  4422.  fields by setting the xLeft and yBottom fields to 0 and the xRight and yTop
  4423.  fields to the pixel width and height of the client window. This function was
  4424.  convenient in the earlier programs because they used WinDrawText to display
  4425.  centered text in the client window and could simply pass the RECTL pointer
  4426.  directly to DrawText.
  4427.  
  4428.  But SYSVALS1 doesn't use the WinDrawText function. WinDrawText works well
  4429.  for displaying text within a rectangle, but it's less suitable for
  4430.  displaying multiple lines of text, as SYSVALS1 does. Instead, SYSVALS1 uses
  4431.  the GPI function GpiCharStringAt to display the text, and GpiCharStringAt
  4432.  doesn't use the RECTL structure.
  4433.  
  4434.  Moreover, the approach used in the previous programs required that the
  4435.  WinQueryWindowRect function be called when processing every WM_PAINT
  4436.  message. It's more efficient to obtain the size of the client window only
  4437.  when the size changes. How do you know when the size of the client window
  4438.  changes? Simple──the Presentation Manager sends a message to the client
  4439.  window procedure. That message is WM_SIZE.
  4440.  
  4441.  The window procedure receives the first WM_SIZE message during the
  4442.  WinCreateStdWindow call. Thereafter, the window procedure receives a WM_SIZE
  4443.  message whenever the user changes the window's size, either by using the
  4444.  sizing border or by maximizing or minimizing the window. The mp1 and mp2
  4445.  parameters that accompany a WM_SIZE message indicate the previous size of
  4446.  the client window and the new size of the window. The width and height of
  4447.  the window are given in pixels. These values are encoded in mp1 and mp2 as
  4448.  shown in Figure 4-3.
  4449.  
  4450.  WM_SIZE is a good example of a message that encodes two unsigned short
  4451.  integers (the USHORT type) in a 32-bit far pointer (the MPARAM type). To
  4452.  help you extract the two USHORTs from the MPARAM, the PMWIN.H header file
  4453.  contains two macros: SHORT1FROMMP and SHORT2FROMMP. These are defined as
  4454.  follows:
  4455.  
  4456.    #define SHORT1FROMMP(mp) ((USHORT) (ULONG) (mp))
  4457.    #define SHORT2FROMMP(mp) ((USHORT) ((ULONG) mp >> 16))
  4458.  
  4459.  Figure 4-3.  The WM_SIZE mp1 and mp2 parameters.
  4460.  
  4461.            ┌──┬──┬──┬───┬──┬──┬──┬──┬──┬──┬───┬──┬──┬──┐
  4462.        mp1 │31│30│29│...│18│17│16│15│14│13│...│ 2│ 1│ 0│
  4463.            └──┴──┴──┴───┴──┴──┴──┴──┴──┴──┴───┴──┴──┴──┘
  4464.            └──────────┬──────────╨──────────┬──────────┘
  4465.                    Previous              Previous
  4466.                     height                width
  4467.                   in pixels             in pixels
  4468.  
  4469.            ┌──┬──┬──┬───┬──┬──┬──┬──┬──┬──┬───┬──┬──┬──┐
  4470.        mp2 │31│30│29│...│18│17│16│15│14│13│...│ 2│ 1│ 0│
  4471.            └──┴──┴──┴───┴──┴──┴──┴──┴──┴──┴───┴──┴──┴──┘
  4472.            └──────────┬──────────╨──────────┬──────────┘
  4473.                      New                   New
  4474.                     height                width
  4475.                   in pixels             in pixels
  4476.  
  4477.  
  4478.  For example, you can obtain the new height of the client window with
  4479.  
  4480.    SHORT2FROMMP (mp2)
  4481.  
  4482.  You should use these macros rather than your own code to extract the USHORT
  4483.  values. On some future implementations of the Presentation Manager, the two
  4484.  USHORT values might be encoded in the MPARAM in a different way. The macros
  4485.  insulate you from the implementation.
  4486.  
  4487.  Processing the WM_SIZE message is simple. In the client window procedure,
  4488.  you define two static variables named cxClient and cyClient (for example) to
  4489.  store the width and height of the client window:
  4490.  
  4491.    static SHORT cxClient, cyClient ;
  4492.  
  4493.  An x prefix to a variable name usually indicates a horizontal position; a y
  4494.  prefix indicates a vertical position. The c prefix stands for "count," and
  4495.  when combined with x indicates a width and with y a height. Here's how the
  4496.  SYSVALS1 program processes the WM_SIZE message:
  4497.  
  4498.    case WM_SIZE:
  4499.         cxClient = SHORT1FROMMP (mp2) ;
  4500.         cyClient = SHORT2FROMMP (mp2) ;
  4501.         return 0 ;
  4502.  
  4503.  The cxClient and cyClient variables must be defined as static because they
  4504.  are used later when processing other messages. After the first WM_SIZE
  4505.  message, the window procedure always has access to a valid client window
  4506.  size. In most cases you won't need to store or use the previous window size.
  4507.  You'll find similar WM_SIZE processing in most of the programs in this book.
  4508.  (Although the SHORT1FROMMP and SHORT2FROMMP macros extract unsigned short
  4509.  integer values from mp1 and mp2, the values are stored in cxClient and
  4510.  cyClient, which are defined as signed short integers. As you'll see, the
  4511.  cxClient and cyClient are often used in arithmetic manipulations for which
  4512.  the SHORT definition is safer.)
  4513.  
  4514.  The Presentation Space
  4515.  
  4516.  To write to the client window, you need a handle to a "presentation space."
  4517.  (A presentation space is a data structure that describes an abstract display
  4518.  surface.) The presentation space handle is the first parameter to virtually
  4519.  all GPI functions and is your permission slip to use the various GPI drawing
  4520.  functions. The presentation space contains certain "attributes" that
  4521.  determine how the GPI functions work. These attributes all have default
  4522.  values when the presentation space is first created. You can change these
  4523.  attributes with GPI functions, but often the defaults are the most
  4524.  convenient attributes.
  4525.  
  4526.  For example, in SYSVALS1 we probably want to display black text on a white
  4527.  background. These colors are attributes defined in the presentation space,
  4528.  and the defaults are black text on a white background. (Actually, the
  4529.  default colors are a little more complex than simply black and white, but
  4530.  I'll discuss that in Chapter 5.) We want the text to run from left to right
  4531.  rather than right to left or top to bottom or bottom to top; this also is
  4532.  defined by the default presentation space. We want the letters of the text
  4533.  string to be positioned top side up and not tilted in some way; the default
  4534.  presentation space attributes define the characters to be displayed like
  4535.  this. The presentation space also defines the font used to display text. In
  4536.  the default presentation space, this is a font known as the "system font,"
  4537.  which is the same font that the Presentation Manager uses for text in title
  4538.  bars, menus, message boxes, and dialog boxes. The system font is a
  4539.  "proportionally spaced" Helvetica font. This means that characters have
  4540.  different widths. For example, a W is about three and one-half times wider
  4541.  than an I. Working with a proportionally spaced font certainly adds a layer
  4542.  of complexity to text output, but nothing insurmountable.
  4543.  
  4544.  In this book, I'll most often use the type of presentation space called the
  4545.  "cached micro-PS." The cached micro-PS gives a program access to only a
  4546.  subset of the GPI functions, but it is often easier to use in small
  4547.  programs.
  4548.  
  4549.  Because a presentation space defines an abstract drawing surface, it isn't
  4550.  very useful by itself (unless, of course, you own an abstract display or an
  4551.  abstract printer). This is why a presentation space is usually "associated
  4552.  with" a particular "device context." The device context refers to a device
  4553.  driver and the physical output device, such as the video display, a printer,
  4554.  or a plotter. (A device context can also describe an output device that
  4555.  isn't quite real, such as a "memory device context," in which a block of
  4556.  memory mimics a real display surface, or a "metafile device context," in
  4557.  which the graphics drawing functions are collected in a file.) Here's a
  4558.  simplified description of the relationship between a presentation space and
  4559.  a device context:
  4560.  
  4561.    ■  The presentation space describes an abstract drawing surface.
  4562.  
  4563.    ■  The device context describes a physical output device.
  4564.  
  4565.    ■  When the presentation space is associated with the device context, what
  4566.       you draw on the presentation space by calling GPI functions will appear
  4567.       on the device.
  4568.  
  4569.  The cached micro-PS, however, is always associated with the device context
  4570.  for the video display. More specifically, the cached micro-PS applies only
  4571.  to a particular window on the video display, typically your client window.
  4572.  When you obtain a handle to a cached micro-PS, you can't draw outside this
  4573.  window. It's not an error if you try to do so──the Presentation Manager
  4574.  simply ignores the attempt.
  4575.  
  4576.  Getting a Handle to a Presentation Space
  4577.  
  4578.  In using a cached micro-PS, you obtain the handle to the presentation space
  4579.  when you need to draw, and you "release" the handle when you finish drawing.
  4580.  After you release the handle, it's no longer valid. You have to obtain a new
  4581.  handle when you want to draw again. You should obtain and release the
  4582.  presentation space handle while processing a single message. You should not
  4583.  obtain the handle while processing one message and release it while
  4584.  processing another. Each time you obtain the handle, all attributes of the
  4585.  presentation space are set to default values. Changes you make to these
  4586.  attributes are lost when you release the handle.
  4587.  
  4588.  In your window procedure, you define a variable (usually called hps) that is
  4589.  of type HPS, a handle to a presentation space:
  4590.  
  4591.    HPS  hps ;
  4592.  
  4593.  There are two methods for obtaining a cached micro-PS handle for your client
  4594.  window. The SYSVALS1 program uses both methods.
  4595.  
  4596.  Method one: during processing of the WM_PAINT message
  4597.  The first way to obtain a cached micro-PS handle is while processing the
  4598.  WM_PAINT message:
  4599.  
  4600.    case WM_PAINT:
  4601.         hps = WinBeginPaint (hwnd, NULL, NULL) ;
  4602.              [call GPI functions]
  4603.         WinEndPaint (hps) ;
  4604.         return 0 ;
  4605.  
  4606.  You should always call WinBeginPaint and WinEndPaint as a pair. Don't call
  4607.  WinBeginPaint and WinEndPaint while processing messages other than WM_PAINT.
  4608.  By setting the second parameter of WinBeginPaint to NULL, you request a
  4609.  cached micro-PS handle. Otherwise, you would set this parameter to the
  4610.  noncached presentation space handle you obtain from GpiCreatePS (a function
  4611.  I'll touch on in Chapters 6 and 7).
  4612.  
  4613.  The window procedure receives a WM_PAINT message only when part of the
  4614.  window is invalid and must be repainted. For example, if part of your
  4615.  program's client window is partly off the screen and the user then moves the
  4616.  window so it is entirely within the screen, the area previously off the
  4617.  screen is marked as invalid. The Presentation Manager posts a WM_PAINT
  4618.  message in the window procedure's message queue.
  4619.  
  4620.  The third parameter to WinBeginPaint is an optional pointer to a RECTL
  4621.  structure to obtain the coordinates of the rectangle encompassing the
  4622.  invalid area. (We'll use this in the SYSVALS3 version of the program.) The
  4623.  presentation space handle you obtain from WinBeginPaint allows you to draw
  4624.  only within this rectangle. When you call WinEndPaint, the Presentation
  4625.  Manager validates the entire area of the window.
  4626.  
  4627.  Method two: during processing of other messages
  4628.  You can also obtain a cached micro-PS handle while processing messages other
  4629.  than WM_PAINT:
  4630.  
  4631.    hps = WinGetPS (hwnd) ;
  4632.         [call GPI functions]
  4633.    WinReleasePS (hps) ;
  4634.  
  4635.  You should always call WinGetPS and WinReleasePS as a pair. With the handle
  4636.  from WinGetPS, you can draw on any part of the client window. However,
  4637.  unlike WinEndPaint, WinReleasePS doesn't validate any part of the window.
  4638.  SYSVALS1 calls WinGetPS and WinReleasePS while processing the WM_CREATE
  4639.  message. I'll describe shortly what the program does during that message.
  4640.  
  4641.  The Coordinate System
  4642.  
  4643.  Parameters to GPI functions often specify coordinate positions and sizes.
  4644.  Several attributes of the presentation space define the coordinate system in
  4645.  effect when you draw; that is, they determine how the coordinate positions
  4646.  and sizes you specify in GPI functions are translated and mapped to the
  4647.  pixels of the output device. By default, coordinates and sizes for a cached
  4648.  micro-PS are specified in units of pixels, and coordinates are relative to
  4649.  the lower-left corner of the window, regardless of where the window is
  4650.  positioned on the screen. Values on the horizontal (or x) axis increase to
  4651.  the right; values on the vertical (or y) axis increase going up.
  4652.  
  4653.  The notation (x, y) is often used to indicate a particular point in x and y
  4654.  coordinates. The point (0, 0) is the lower-left corner of the client window.
  4655.  If you set variables cxClient and cyClient while processing the WM_SIZE
  4656.  message, the upper-right of the client window is (cxClient - 1, cyClient -
  4657.  1). The coordinate system for a cached micro-PS is shown in Figure 4-4.
  4658.  
  4659.  Figure 4-4.  The cached micro-PS default coordinate system.
  4660.  
  4661.      y=(cyClient-1)
  4662.            │   ┌──────┬───────────────────────────────────┬───┬───┐
  4663.            │   │ ──── │             Title bar             │  │  │
  4664.            │   ├──────┴───────────────────────────────────┴───┴───┤
  4665.            └──│                                                  │
  4666.                │                                                 │
  4667.                │ │                                                │
  4668.                │ │                                                │
  4669.                │ │                                                │
  4670.                │ │                                                │
  4671.                │ │                                                │
  4672.                │ │                                                │
  4673.                │ │                                                │
  4674.                │ │                                                │
  4675.                │ │                                                │
  4676.                │ │                                                │
  4677.        y=0────│ └────────────────────                           │
  4678.                └──────────────────────────────────────────────────┘
  4679.                                                                 
  4680.                  │                                               │
  4681.                 x=0                                  x=(cxClient-1)
  4682.  
  4683.  
  4684.  The Size of a Character
  4685.  Because this coordinate system has an origin at the lower-left corner of the
  4686.  client area, it is somewhat inconvenient for displaying text, which most of
  4687.  us read from the top down. But that's a relatively simple adjustment you can
  4688.  make when it comes time to display the text. The GPI function used in
  4689.  SYSVALS1 to display text is GpiCharStringAt. This function requires the x
  4690.  and y coordinates of the starting position of the text. SYSVALS1 calls
  4691.  GpiCharStringAt three times──once for each of the three columns to be
  4692.  displayed. Thus, to properly space successive lines and columns of text,
  4693.  SYSVALS1 needs to know the height and width of the characters in pixels.
  4694.  
  4695.  When you obtain a handle to a cached micro-PS, the default presentation
  4696.  space includes a font. Unless you change that font, the Presentation Manager
  4697.  uses that font for all text you write to the presentation space. The default
  4698.  font is called the "system font." This is the proportionally spaced font
  4699.  used for normal text in the Presentation Manager. You can obtain character
  4700.  dimensions of the current font in the presentation space by calling
  4701.  GpiQueryFontMetrics. You first define a structure of type FONTMETRICS:
  4702.  
  4703.    FONTMETRICS fm ;
  4704.  
  4705.  Then you call the function:
  4706.  
  4707.    GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm);
  4708.  
  4709.  The second parameter is the size of the structure in bytes, and the last
  4710.  parameter is a pointer to the structure.
  4711.  
  4712.  On the function's return, the fields of the fm structure describe many of
  4713.  the basic characteristics of the font. Figure 4-5 shows the fields that
  4714.  describe the dimensions of characters. Obviously, these fields represent
  4715.  much more information than you need right now, but they give you a sense of
  4716.  just how much information is available.
  4717.  
  4718.  All these values are LONG (32-bit) integers, as indicated by the "l" prefix.
  4719.  
  4720.  Figure 4-5.  The character dimension fields from GpiQueryFontMetrics.
  4721.  
  4722.                                                                       ─┐
  4723.                                                                       ─┤lExter
  4724.     ▄   ▄                                                             ─┤lInter
  4725.               ▄                                           ─┐           │
  4726.     ▄ ▄ ▄     ▄                                 ─┐         │           │
  4727.   ▄       ▄   ▄                                  │         │           │lMaxAs
  4728.  ▄         ▄  ▄                                  │         │lLowerCase-│
  4729.  ▄         ▄  ▄ ▄ ▄ ▄ ▄     ▄ ▄ ▄ ▄ ▄ ─┐         │lEmHeight│Ascent     │
  4730.  ▄ ▄ ▄ ▄ ▄ ▄  ▄        ▄  ▄         ▄  │lXHeight │(average)│           │
  4731.  ▄         ▄  ▄        ▄  ▄         ▄  │(average)│         │           │
  4732.  ▄         ▄  ▄ ▄ ▄ ▄ ▄     ▄ ▄ ▄ ▄ ▄  │         │         │           │
  4733.                                     ▄ ─┴─────────┴─────────┼───────────┼──(Bas
  4734.  └─────────┘                        ▄                      │lLowerCase-│
  4735.    lEmInc                   ▄       ▄                      │Descent    │lMaxDe
  4736.                               ▄ ▄ ▄                       ─┘          ─┘
  4737.                                                                       lMaxBase
  4738.                           └───────────┘
  4739.                           lAveCharWidth
  4740.                                or
  4741.                            lMaxCharInc
  4742.  
  4743.  
  4744.  Character width
  4745.  For a proportionally spaced font like the system font, the FONTMETRICS
  4746.  structure provides two fields that are valuable. The lAveCharWidth field is
  4747.  the weighted average width of lowercase letters based on the frequency of
  4748.  these letters in English. The lEmInc field is a weighted average width of
  4749.  uppercase letters. (The FONTMETRICS structure also includes a field called
  4750.  lMaxCharInc, which is the width of the widest character.) In all cases the
  4751.  width includes intercharacter spacing.
  4752.  
  4753.  Character height
  4754.  When it comes to character heights, the FONTMETRICS structure provides more
  4755.  detailed information. The lXHeight value is the average height above the
  4756.  baseline of a lowercase letter without ascenders, and lEmHeight is the
  4757.  average height of an uppercase letter. Depending on the typeface, the
  4758.  lLowerCaseAscent value could be less than lEmHeight, greater than lEmHeight,
  4759.  or the same. The lInternalLeading field is the space reserved for
  4760.  diacritics. For some fonts, it could be zero. The lExternalLeading field is
  4761.  the amount of white space recommended by the designer of the font to be
  4762.  added between lines of text. For some fonts, this also can be zero.
  4763.  
  4764.  Interline spacing
  4765.  For spacing successive lines of text, use the value returned in the
  4766.  lMaxBaselineExt field. You can also use lMaxBaselineExt less
  4767.  lExternalLeading if you want to get more lines of text into a smaller space.
  4768.  Earlier I noted that the Presentation Manager requires the screen to display
  4769.  at least 24 lines of 80 characters. For some low-resolution displays that
  4770.  holds true only if you space lines of text using the lMaxBaselineExt values
  4771.  minus lExternalLeading.
  4772.  
  4773.  The size of the system font won't change during the time your program is
  4774.  running, so you need to obtain the character sizes only once. An excellent
  4775.  time to do this is while processing the WM_CREATE message, which is the
  4776.  approach that SYSVALS1 uses. SYSVALS1 defines four static variables to hold
  4777.  the average lowercase width, average uppercase width, total height, and
  4778.  descender height of a character:
  4779.  
  4780.    static SHORT cxChar, cxCaps, cyChar, cyDesc, cxClient, cyClient ;
  4781.  
  4782.  While processing WM_CREATE, SYSVALS1 obtains a handle to the presentation
  4783.  space, calls WinQueryFontMetrics, and saves the values of the lAveCharWidth,
  4784.  lMaxBaselineExt, and lMaxDescender fields:
  4785.  
  4786.    case WM_CREATE:
  4787.         hps = WinGetPS (hwnd) ;
  4788.  
  4789.         GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  4790.  
  4791.         cxChar = (SHORT) fm.lAveCharWidth ;
  4792.         cxCaps = (SHORT) fm.lEmInc ;
  4793.         cyChar = (SHORT) fm.lMaxBaselineExt ;
  4794.         cyDesc = (SHORT) fm.lMaxDescender ;
  4795.  
  4796.         WinReleasePS (hps) ;
  4797.         return 0 ;
  4798.  
  4799.  Like the processing of the WM_SIZE message, this is fairly standard code;
  4800.  you'll see it frequently in Presentation Manager programs that work with
  4801.  simple text.
  4802.  
  4803.  During the WM_CREATE message, SYSVALS1 obtains a handle to the presentation
  4804.  space only to obtain information. Attempting to draw during the WM_CREATE
  4805.  message is unwise, because the window isn't yet displayed on the screen.
  4806.  (And be forewarned that the FONTMETRICS structure is over 200 bytes long.
  4807.  For purposes of clarity, I've defined fm as a local variable in
  4808.  ClientWndProc. In most programs, the definition of fm and the
  4809.  GpiQueryFontMetrics call should probably be moved to a subroutine so the
  4810.  structure doesn't take up stack space whenever the window procedure is
  4811.  called.)
  4812.  
  4813.  The WM_PAINT Message
  4814.  
  4815.  WM_PAINT is an extremely important message. The window procedure receives a
  4816.  WM_PAINT message when an area of the window becomes invalid. This can happen
  4817.  frequently as the user moves and resizes various windows on the screen. Your
  4818.  Presentation Manager programs should be structured so that they can entirely
  4819.  update the client window on receipt of a WM_PAINT message. In many cases,
  4820.  this means the program can be most efficient if it draws on the client
  4821.  window only during the WM_PAINT message.
  4822.  
  4823.  This certainly isn't a hard-and-fast rule. Obviously, the program can access
  4824.  a presentation space by calling WinGetPS and paint on the client window at
  4825.  almost any time. But the program must be able to entirely repaint the client
  4826.  window when it receives the WM_PAINT message anyway, so any drawing it does
  4827.  during other messages has to be duplicated during WM_PAINT processing.
  4828.  Often, however, a window procedure will determine during a message other
  4829.  than WM_PAINT that part of the client window should be changed. We'll see
  4830.  examples of how programs can themselves generate WM_PAINT messages in the
  4831.  SYSVALS2 and SYSVALS3 programs presented in this chapter.
  4832.  
  4833.  The GpiCharStringAt Function
  4834.  
  4835.  SYSVALS1 uses the GpiCharStringAt function to write text to the client area.
  4836.  The At part of the function name indicates that the function requires
  4837.  specific coordinates for where the text is to begin. The general syntax of
  4838.  GpiCharStringAt is
  4839.  
  4840.    GpiCharStringAt (hps, &ptl, lCount, pchString) ;
  4841.  
  4842.  The first parameter is a handle to the presentation space. That's the case
  4843.  for virtually all GPI functions. The last parameter is a pointer to a
  4844.  character string (as indicated by the "pch" prefix). The third parameter is
  4845.  a LONG value of the number of characters in the string. Unlike WinDrawText,
  4846.  GpiCharStringAt doesn't recognize zero-terminated character strings.
  4847.  
  4848.  The second parameter to GpiCharStringAt is a pointer to a structure of type
  4849.  POINTL (which stands for "a point with LONG coordinates"). The definition of
  4850.  the POINTL structure in OS2DEF.H looks like this:
  4851.  
  4852.    typedef struct _POINTL
  4853.         {
  4854.         LONG x ;
  4855.         LONG y ;
  4856.         }
  4857.         POINTL ;
  4858.  
  4859.  It's simply a structure that specifies a point in terms of x and y
  4860.  coordinates. You can define a variable (the name ptl is standard) of type
  4861.  POINTL in your window procedure:
  4862.  
  4863.    POINTL ptl ;
  4864.  
  4865.  You then set the x and y fields of this structure before you call
  4866.  GpiCharStringAt. These x and y values indicate the starting position of the
  4867.  string, specifically the point corresponding to the baseline of the left
  4868.  side of the first character. If you use GpiCharStringAt with the string
  4869.  "go", for example, it's displayed relative to the lower-left corner of the
  4870.  client window, as shown in Figure 4-6.
  4871.  
  4872.  Figure 4-6.  A character string with starting coordinates set by the
  4873.               GpiCharStringAt function.
  4874.  
  4875.                       
  4876.                       │
  4877.                       │
  4878.                       │
  4879.                       │
  4880.                       │             ▄ ▄ ▄ ▄ ▄    ▄ ▄ ▄ ▄
  4881.                       │           ▄         ▄  ▄         ▄
  4882.                       │           ▄         ▄  ▄         ▄
  4883.               ptl.y───┼──────────┐  ▄ ▄ ▄ ▄ ▄    ▄ ▄ ▄ ▄
  4884.                       │          │          ▄
  4885.                       │          │          ▄
  4886.                       │          │  ▄       ▄
  4887.                       │          │    ▄ ▄ ▄
  4888.                       │          │
  4889.                       │          │
  4890.                       │          │
  4891.                       └──────────┼──────────────────────
  4892.                                  │
  4893.                                ptl.x
  4894.  
  4895.  
  4896.  The use of the baseline for the y coordinate can be a little tricky. For
  4897.  example, to display the string "go" in the lower-left corner of the client
  4898.  window, you might want to use code like this:
  4899.  
  4900.    ptl.x = 0 ;
  4901.    ptl.y = 0 ;
  4902.  
  4903.    GpiCharStringAt (hps, &ptl, (LONG) 2, "go") ;
  4904.  
  4905.  But the descender on the g won't be visible. Instead, you need to adjust the
  4906.  y coordinates for the length of the descender:
  4907.  
  4908.    gpt.y = cyDesc ;
  4909.  
  4910.  Numeric Formatting
  4911.  
  4912.  If you felt disheartened when I announced at the beginning of this chapter
  4913.  that printf can't be used in Presentation Manager programs, cheer up and
  4914.  take a look at sprintf. Like printf, sprintf formats numbers and text based
  4915.  on a formatting string. However, rather than writing the resultant formatted
  4916.  text to standard output, sprintf stores it in a character buffer that you
  4917.  provide. The general syntax is
  4918.  
  4919.    iLength = sprintf (szBuffer, szFormat, ...) ;
  4920.  
  4921.  where iLength is the integer length of the zero-terminated output string
  4922.  that sprintf stores in szBuffer.
  4923.  
  4924.  When you use sprintf in a Presentation Manager program, include the STDIO.H
  4925.  header file at the top of the C source code file:
  4926.  
  4927.    #include <stdio.h>
  4928.  
  4929.  You must also define a buffer large enough for the formatted text. For
  4930.  example:
  4931.  
  4932.    CHAR szBuffer [80] ;
  4933.  
  4934.  You can then use sprintf with GpiCharStringAt like this:
  4935.  
  4936.    iLength = sprintf (szBuffer, "The sum of %d and &d is %d",
  4937.                       iNum1, iNum2, iNum1 + iNum2) ;
  4938.  
  4939.    GpiCharStringAt (hps, &ptl, (LONG) iLength, szBuffer) ;
  4940.  
  4941.  Or you can dispense with the iLength variable and combine both statements
  4942.  into one:
  4943.  
  4944.    GpiCharStringAt (hps, &ptl,
  4945.              (LONG) sprintf (szBuffer, "The sum of %d and %d is %d",
  4946.                                  iNum1, iNum2, iNum1 + iNum2),
  4947.              szBuffer) ;
  4948.  
  4949.  This may look ugly, but it's a common construction in Presentation Manager
  4950.  programs.
  4951.  
  4952.  But sprintf is overkill for SYSVALS1. Instead, the program can display text
  4953.  strings by passing them directly as the last parameter to GpiCharStringAt
  4954.  and using strlen to find the length of each string (required for the third
  4955.  parameter to GpiCharStringAt). For formatting the value returned from
  4956.  WinQuerySysValue, SYSVALS1 can use the C ltoa function.
  4957.  
  4958.  At this point, the processing of the WM_PAINT message in SYSVALS1 should be
  4959.  almost comprehensible:
  4960.  
  4961.    case WM_PAINT:
  4962.         hps = WinBeginPaint (hwnd, NULL, NULL) ;
  4963.         GpiErase (hps) ;
  4964.  
  4965.         for (sLine = 0 ; sLine < NUMLINES ; sLine++)
  4966.              {
  4967.              ptl.x = cxCaps ;
  4968.              ptl.y = cyClient - cyChar * (sLine + 1) + cyDesc ;
  4969.  
  4970.              GpiCharStringAt (hps, &ptl,
  4971.                        (LONG) strlen (sysvals[sLine].szIdentifier),
  4972.                        sysvals[sLine].szIdentifier) ;
  4973.  
  4974.              ptl.x += 20 * cxCaps ;
  4975.              GpiCharStringAt (hps, &ptl,
  4976.                        (LONG) strlen (sysvals[sLine].szDescription),
  4977.                        sysvals[sLine].szDescription) ;
  4978.  
  4979.              ltoa (WinQuerySysValue (HWND_DESKTOP,
  4980.                         sysvals[sLine].sIndex), szBuffer, 10) ;
  4981.  
  4982.              ptl.x += 38 * cxChar ;
  4983.              GpiCharStringAt (hps, &ptl, (LONG) strlen (szBuffer),
  4984.                               szBuffer) ;
  4985.              }
  4986.         WinEndPaint (hps) ;
  4987.         return 0 ;
  4988.  
  4989.  Between the WinBeginPaint and WinEndPaint calls is a call to GpiErase (which
  4990.  erases the invalid rectangle) and a simple for loop. The NUMLINES identifier
  4991.  is defined in SYSVALS.H.
  4992.  
  4993.  The x field of the POINTL structure is initially set to cxCaps. Thus every
  4994.  line is indented one character width from the left side of the client
  4995.  window. For the first line (sLine equals 0), the y field is set to (cyClient
  4996.  - cyChar + cyDesc), the top line of the client window. Each successive line
  4997.  begins yChar pixels below the previous line. The first GpiCharStringAt call
  4998.  displays the szIdentifier field of the sysvals structure (for example,
  4999.  SV_SWAPBUTTON). For the second GpiCharStringAt call, the x field of the
  5000.  POINTL structure is increased by 20 times the average width of an uppercase
  5001.  letter:
  5002.  
  5003.    ptl.x += 20 * cxCaps ;
  5004.  
  5005.  The szDescription field is then displayed. SYSVALS1 converts the value
  5006.  obtained from WinQuerySysValue by calling ltoa. It moves the x field of the
  5007.  POINTL structure to the right of the description column:
  5008.  
  5009.    ptl.x += 38 * cxChar ;
  5010.  
  5011.  It then displays the value.
  5012.  
  5013.  The Problem with SYSVALS1
  5014.  
  5015.  So that's it──SYSVALS1 obtains the width and height of a system font
  5016.  character while processing the WM_CREATE message, obtains the width and
  5017.  height of the client window from the WM_SIZE message, and paints the client
  5018.  window using this information during WM_PAINT. It's simple, and it's wrong
  5019.  ──on most standard video displays, there's not enough room to display all
  5020.  48 values obtained from WinQuerySysValue. SYSVALS1 always displays the
  5021.  values starting at the top of its client window and has no way to bring the
  5022.  hidden lines into view. That's a problem. But it's nothing a scroll bar
  5023.  can't fix.
  5024.  
  5025.  
  5026.  Adding Scroll Bars
  5027.  
  5028.  Scroll bars are an important part of the consistent user interface in the
  5029.  Presentation Manager. For users, scroll bars are easy to learn and to use,
  5030.  and they provide good visual feedback. Scroll bars are usually thought of as
  5031.  controlling the view of a document, as in a word-processing program, but
  5032.  they can be used in any program that has more to display than can fit in the
  5033.  client window. A vertical scroll bar, like the one shown in Figure 4-7,
  5034.  is normally positioned to the right of the client window.
  5035.  
  5036.  Figure 4-7.  A vertical scroll bar and the actions it performs.
  5037.  
  5038.                                            ┌───┐
  5039.                 Click here to move         │  │
  5040.                        up one line────────│ │ │
  5041.                                            ├───┤
  5042.                 Click here to move         │▒▒▒│
  5043.                        up one page────────│▒▒▒│
  5044.                                            │▒▒▒│
  5045.                                            │▒▒▒│
  5046.                 Drag the slider to         ├───┤
  5047.                 scroll to position────────│   │
  5048.                                            ├───┤
  5049.                                            │▒▒▒│
  5050.                 Click here to move         │▒▒▒│
  5051.                        down one page──────│▒▒▒│
  5052.                                            │▒▒▒│
  5053.                                            ├───┤
  5054.                 Click here to move         │ │ │
  5055.                        down one line──────│  │
  5056.                                            └───┘
  5057.  
  5058.  
  5059.  A click on the arrow at the top of the scroll bar moves the view one line
  5060.  toward the beginning of the document. (This is called "scrolling up" in
  5061.  keeping with the user's perspective, even though the document actually
  5062.  scrolls down relative to the window.) Similarly, a click on the bottom arrow
  5063.  moves the view one line toward the end of the document.
  5064.  
  5065.  Between the two arrows is a long area containing the moveable scroll-bar
  5066.  slider. Clicking above the slider moves the view one page toward the
  5067.  beginning of the document; clicking below the slider moves the view one page
  5068.  toward the end. The slider indicates the approximate position within the
  5069.  entire document of the portion displayed on the screen. You can move to a
  5070.  position in the document by dragging the slider to the relative spot in the
  5071.  slider area. For example, you can move to the beginning of the document by
  5072.  dragging the slider to the top of the slider area.
  5073.  
  5074.  Horizontal scroll bars (normally positioned at the bottom of a client
  5075.  window) are used in a similar fashion to scroll documents left and right.
  5076.  
  5077.  Creating the Scroll Bar
  5078.  
  5079.  The first step in adding a scroll-bar interface involves changing a
  5080.  parameter to the WinCreateStdWindow call. You simply include the necessary
  5081.  frame creation flag identifier (FCF_HORZSCROLL, FCF_VERTSCROLL, or both) in
  5082.  the flFrameFlags variable. The Presentation Manager creates the scroll-bar
  5083.  windows as children of the frame window. With only this change, the scroll
  5084.  bars don't seem to do very much. The scroll bar colors itself with a
  5085.  reverse-video flash when you click on it, but that's about it.
  5086.  
  5087.  Looks are deceiving. When you click on a scroll bar, the scroll-bar window
  5088.  procedure (located in the Presentation Manager) receives a mouse message.
  5089.  The scroll bar then posts a notification message to its owner, which is the
  5090.  frame window. This notification message contains information about the
  5091.  action of the mouse on the scroll bar. The frame window graciously sends
  5092.  this message to the client window procedure, which is in your program. The
  5093.  notification messages are WM_HSCROLL for a horizontal scroll bar and
  5094.  WM_VSCROLL for a vertical scroll bar.
  5095.  
  5096.  Your client window procedure can also send messages to the scroll-bar
  5097.  window. These messages set the "range" and current "position" of the scroll
  5098.  bar slider. To send these messages, you need to know the window handle of
  5099.  the scroll bar. When the Presentation Manager creates the scroll bars as
  5100.  part of the standard window, they are assigned predefined child ID numbers
  5101.  of FID_HORZSCROLL and FID_VERTSCROLL. Thus you can obtain the window handle
  5102.  of horizontal and vertical scroll bars by calling
  5103.  
  5104.    hwndHscroll = WinWindowFromID (hwndFrame, FID_HORZSCROLL) ;
  5105.    hwndVscroll = WinWindowFromID (hwndFrame, FID_VERTSCROLL) ;
  5106.  
  5107.  The scroll bars' parent is hwndFrame. The frame window is also the parent of
  5108.  the client window, so you can also obtain these handles within your client
  5109.  window procedure by using only the hwnd parameter passed to the procedure.
  5110.  You'll probably do this while processing the WM_CREATE message:
  5111.  
  5112.    hwndHscroll = WinWindowFromID (
  5113.                        WinQueryWindow (hwnd, QW_PARENT, FALSE),
  5114.                        FID_HORZSCROLL) ;
  5115.  
  5116.    hWndVscroll = WinWindowFromID (
  5117.                        WinQueryWindow (hwnd, QW_PARENT, FALSE),
  5118.                        FID_VERTSCROLL) ;
  5119.  
  5120.  Within a client window procedure, these window handles should be stored in
  5121.  static variables of type HWND.
  5122.  
  5123.  The Range and Position
  5124.  
  5125.  After obtaining the window handle of a scroll bar, the program can
  5126.  initialize the scroll bar to a range and slider position. When first
  5127.  created, a scroll bar has a default range of 0 to 100. The position of the
  5128.  scroll-bar slider is always a discrete integral value within this range:
  5129.  
  5130.    ■  If the slider is at the top (or left) of the scroll bar, the position
  5131.       is 0.
  5132.  
  5133.    ■  If the slider is at the bottom (or right) of the scroll bar, the
  5134.       position is 100.
  5135.  
  5136.  If that 0 to 100 range isn't appropriate for your program, you can set a
  5137.  different range by sending the scroll bar an SBM_SETSCROLLBAR message.
  5138.  SBM_SETSCROLLBAR, like other messages that begin with SBM ("scroll-bar
  5139.  message"), is a message understood only by scroll bars. Set the mp1
  5140.  parameter of this message to the initial position of the scroll-bar slider.
  5141.  Set mp2 to contain the range of the scroll bar, with the minimum value in
  5142.  the low half of mp2 and the maximum value in the high half. You can convert
  5143.  these values to an MPARAM data type using the MPFROM2SHORT macro. For
  5144.  example, suppose you want to set the vertical scroll-bar range to 10 through
  5145.  40 and the initial position to 15. Here's the code:
  5146.  
  5147.    sMinPos = 10 ;
  5148.    sMaxPos = 40 ;
  5149.    sPosition = 15 ;
  5150.  
  5151.    WinSendMsg (hwndVscroll, SBM_SETSCROLLBAR,
  5152.                        MPFROM2SHORT (sPosition, 0),
  5153.                        MPFROM2SHORT (sMinPos, sMaxPos)) ;
  5154.  
  5155.  If you ever need to obtain the range from the scroll bar, you can do so by
  5156.  sending the scroll bar an SBM_QUERYRANGE message:
  5157.  
  5158.    mr = WinSendMsg (hwndVscroll, SBM_QUERYRANGE, NULL, NULL) ;
  5159.  
  5160.  The minimum and maximum range positions are encoded in mr (a variable of
  5161.  type MRESULT) and can be extracted using the SHORT1FROMMR and SHORT2FROMMR
  5162.  macros:
  5163.  
  5164.    sMinPos = SHORT1FROMMR (mr) ;
  5165.    sMaxPos = SHORT2FROMMR (mr) ;
  5166.  
  5167.  Receiving Notification Messages from the Scroll Bar
  5168.  
  5169.  Scroll bars post notification messages to their owner (the frame window)
  5170.  when the various parts of the scroll bar are clicked on or dragged. The
  5171.  frame window sends these messages to the client window. For vertical scroll
  5172.  bars, the notification message is WM_VSCROLL; for horizontal scroll bars,
  5173.  it's WM_HSCROLL.
  5174.  
  5175.  Messages from Vertical Scroll Bars
  5176.  The low half of mp1 (which you can obtain using the SHORT1FROMMP macro)
  5177.  contains the child window ID. For a vertical scroll bar created as part of
  5178.  the standard window, this is FID_VERTSCROLL. You need to examine this value
  5179.  only if you create multiple vertical scroll bars as children of your client
  5180.  window. The high half of mp2 indicates the action of the mouse on the scroll
  5181.  bar. The value corresponds to an identifier defined in PMWIN.H that begins
  5182.  with the letter SB. Figure 4-8 shows how these values identify the mouse
  5183.  actions on the vertical scroll bar. The low half of mp2 is the current
  5184.  position of the slider for SB_SLIDERTRACK and SB_SLIDERPOSITION actions.
  5185.  
  5186.  Figure 4-8.  Vertical scroll-bar action identifiers.
  5187.  
  5188.                                                     ┌───┐
  5189.               Press button:    SB_LINEUP            │  │
  5190.               Release button:  SB_ENDSCROLL────────│ │ │
  5191.                                                     ├───┤
  5192.               Press button:    SB_PAGEUP            │   │
  5193.               Release button:  SB_ENDSCROLL────────│   │
  5194.                                                     │   │
  5195.                                                     │   │
  5196.               Press button:    SB_SLIDERTRACK       ├───┤
  5197.               Release button:  SB_SLIDERPOSITION───│   │
  5198.                                                     ├───┤
  5199.                                                     │   │
  5200.               Press button:    SB_PAGEDOWN          │   │
  5201.               Release button:  SB_ENDSCROLL────────│   │
  5202.                                                     │   │
  5203.                                                     ├───┤
  5204.               Press button:    SB_LINEDOWN          │ │ │
  5205.               Release button:  SB_ENDSCROLL────────│  │
  5206.                                                     └───┘
  5207.  
  5208.  
  5209.  Within your client window procedure, you process the WM_VSCROLL message with
  5210.  code that looks like this:
  5211.  
  5212.    case WM_VSCROLL:
  5213.         switch (SHORT2FROMMP (mp2)
  5214.              {
  5215.              case SB_LINEUP:
  5216.                        [process line up action]
  5217.                   break ;
  5218.  
  5219.              case SB_PAGEUP:
  5220.                        [process page up action]
  5221.                   break ;
  5222.                        [and so forth]
  5223.  
  5224.  
  5225.  Messages from Horizontal Scroll Bars
  5226.  Horizontal scroll bars generate messages in the same way vertical scroll
  5227.  bars do: The notification message is WM_HSCROLL, the child window ID is
  5228.  FID_HORZSCROLL, and the identifiers indicating the mouse actions are those
  5229.  shown in Figure 4-9.
  5230.  
  5231.  Figure 4-9.  Horizontal scroll-bar action identifiers.
  5232.  
  5233.         SB_LINELEFT           SB_SLIDERTRACK                 SB_LINERIGHT
  5234.         SB_ENDSCROLL          SB_SLIDERPOSITION              SB_ENDSCROLL
  5235.              │                        │                           │
  5236.              │     SB_PAGELEFT        │        SB_PAGERIGHT       │
  5237.              │     SB_ENDSCROLL       │        SB_ENDSCROLL       │
  5238.              │          │             │             │             │
  5239.              │          │             │             │             │
  5240.                                                               
  5241.           ┌─────┬───────────────────┬────┬─────────────────────┬─────┐
  5242.           │ ── │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│    │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ ── │
  5243.           └─────┴───────────────────┴────┴─────────────────────┴─────┘
  5244.  
  5245.  
  5246.  Processing Scroll-Bar Messages
  5247.  You have some options in how you handle scroll-bar messages. When the user
  5248.  clicks on the arrows or the slider area, you receive at least two WM_VSCROLL
  5249.  or WM_HSCROLL messages. You get the first message when the mouse button is
  5250.  pressed. The action identifier is SB_LINEUP, SB_PAGEUP, SB_LINEDOWN, or
  5251.  SB_PAGEDOWN for vertical scroll bars or one of the similar identifiers for
  5252.  horizontal scroll bars. When the button is released, you receive a
  5253.  WM_VSCROLL or WM_HSCROLL message with the SB_ENDSCROLL action identifier. As
  5254.  a general rule, you process the various "button down" messages and ignore
  5255.  SB_ENDSCROLL.
  5256.  
  5257.  However, if your program requires a lot of time to process these actions,
  5258.  you might want to delay the processing until the mouse button is released.
  5259.  You can simply track how many messages you receive and do something that
  5260.  affects the client window only when you get SB_ENDSCROLL. This approach
  5261.  requires more complex logic and provides less feedback to the user, but it
  5262.  is an alternative.
  5263.  
  5264.  The SB_SLIDERTRACK and SB_SLIDERPOSITION actions can be somewhat
  5265.  troublesome. As the user drags the slider up and down the scroll bar, your
  5266.  window procedure receives many SB_SLIDERTRACK actions.
  5267.  
  5268.    ■  If your program is fast enough, you should process SB_SLIDERTRACK
  5269.       actions and ignore SB_SLIDERPOSITION.
  5270.  
  5271.    ■  If your program has a hard time keeping up, you should process
  5272.       SB_SLIDERPOSITION and ignore SB_SLIDERTRACK.
  5273.  
  5274.  (These two approaches are illustrated later in the chapter: SYSVALS2 is
  5275.  a slow, simple program that processes SB_SLIDERPOSITION; SYSVALS3 is
  5276.  optimized sufficiently to process SB_SLIDERTRACK actions on the vertical
  5277.  scroll bar.)
  5278.  
  5279.  Setting the New Slider Position
  5280.  
  5281.  The scroll-bar window itself never changes the position of the scroll-bar
  5282.  slider unless you tell it to. To change the position of the slider, you send
  5283.  the scroll bar a message. Assume the variable sPosition contains the new
  5284.  position of the vertical scroll bar. You send the scroll bar an SBM_SETPOS
  5285.  message in which mp1 is the new position:
  5286.  
  5287.    WinSendMsg (hwndVscroll, SBM_SETPOS, MPFROMSHORT (sPosition), NULL) ;
  5288.  
  5289.  You typically send the scroll bar the SBM_SETPOS message while processing
  5290.  the WM_VSCROLL or WM_HSCROLL notification message from the scroll bar.
  5291.  
  5292.  If you need to obtain the current position of the scroll-bar slider, you can
  5293.  send the scroll bar an SBM_QUERYPOS message:
  5294.  
  5295.    sPosition = SHORT1FROMMR (WinSendMsg (hwndVScroll,
  5296.                                          SBM_QUERYPOS, NULL, NULL)) ;
  5297.  
  5298.  The Implementation
  5299.  
  5300.  Now we're ready to look at the SYSVALS2 program, shown in Figure 4-10.
  5301.  You'll need the SYSVALS.H header file from Figure 4-1 to compile the
  5302.  program.
  5303.  
  5304.  Figure 4-10.  The SYSVALS2 program.
  5305.  
  5306.    The SYSVALS2 File
  5307.  
  5308.    #--------------------
  5309.    # SYSVALS2 make file
  5310.    #--------------------
  5311.  
  5312.    sysvals2.obj : sysvals2.c sysvals.h
  5313.         cl -c -G2sw -W3 sysvals2.c
  5314.  
  5315.    sysvals2.exe : sysvals2.obj sysvals2.def
  5316.         link sysvals2, /align:16, NUL, os2, sysvals2
  5317.  
  5318.    The SYSVALS2.C File
  5319.  
  5320.    /*---------------------------------------------------
  5321.       SYSVALS2.C -- System Values Display Program No. 2
  5322.      ---------------------------------------------------*/
  5323.  
  5324.    #define INCL_WIN
  5325.    #define INCL_GPI
  5326.    #include <os2.h>
  5327.    #include <stdlib.h>
  5328.    #include <string.h>
  5329.    #include "sysvals.h"
  5330.  
  5331.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  5332.  
  5333.    int main (void)
  5334.         {
  5335.         static CHAR  szClientClass [] = "SysVals2" ;
  5336.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
  5337.                                     FCF_SIZEBORDER    | FCF_MINMAX   |
  5338.                                     FCF_SHELLPOSITION | FCF_TASKLIST |
  5339.                                     FCF_VERTSCROLL ;
  5340.         HAB          hab ;
  5341.         HMQ          hmq ;
  5342.         HWND         hwndFrame, hwndClient ;
  5343.         QMSG         qmsg ;
  5344.  
  5345.         hab = WinInitialize (0) ;
  5346.         hmq = WinCreateMsgQueue (hab, 0) ;
  5347.  
  5348.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  5349.  
  5350.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  5351.                                         &flFrameFlags, szClientClass, NULL,
  5352.                                         0L, NULL, 0, &hwndClient) ;
  5353.  
  5354.         WinSendMsg (hwndFrame, WM_SETICON,
  5355.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  5356.                     NULL) ;
  5357.  
  5358.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  5359.              WinDispatchMsg (hab, &qmsg) ;
  5360.  
  5361.         WinDestroyWindow (hwndFrame) ;
  5362.         WinDestroyMsgQueue (hmq) ;
  5363.         WinTerminate (hab) ;
  5364.         return 0 ;
  5365.         }
  5366.  
  5367.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  5368.         {
  5369.         static HWND  hwndVscroll ;
  5370.         static SHORT cxChar, cxCaps, cyChar, cyDesc,
  5371.                      sVscrollPos, cxClient, cyClient ;
  5372.         CHAR         szBuffer [10] ;
  5373.         FONTMETRICS  fm ;
  5374.         HPS          hps ;
  5375.         POINTL       ptl ;
  5376.         SHORT        sLine ;
  5377.  
  5378.         switch (msg)
  5379.              {
  5380.              case WM_CREATE:
  5381.                   hps = WinGetPS (hwnd) ;
  5382.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  5383.  
  5384.                   cxChar = (SHORT) fm.lAveCharWidth ;
  5385.                   cxCaps = (SHORT) fm.lEmInc ;
  5386.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  5387.                   cyDesc = (SHORT) fm.lMaxDescender ;
  5388.  
  5389.                   WinReleasePS (hps) ;
  5390.  
  5391.                   hwndVscroll = WinWindowFromID (
  5392.                                       WinQueryWindow (hwnd, QW_PARENT, FALSE),
  5393.                                       FID_VERTSCROLL) ;
  5394.  
  5395.                   WinSendMsg (hwndVscroll, SBM_SETSCROLLBAR,
  5396.                                       MPFROM2SHORT (sVscrollPos, 0),
  5397.                                       MPFROM2SHORT (0, NUMLINES - 1)) ;
  5398.                   return 0 ;
  5399.  
  5400.              case WM_SIZE:
  5401.                   cxClient = SHORT1FROMMP (mp2) ;
  5402.                   cyClient = SHORT2FROMMP (mp2) ;
  5403.                   return 0 ;
  5404.  
  5405.              case WM_VSCROLL:
  5406.                   switch (SHORT2FROMMP (mp2))
  5407.                        {
  5408.                        case SB_LINEUP:
  5409.                             sVscrollPos -= 1 ;
  5410.                             break ;
  5411.  
  5412.                        case SB_LINEDOWN:
  5413.                             sVscrollPos += 1 ;
  5414.                             break ;
  5415.  
  5416.                        case SB_PAGEUP:
  5417.                             sVscrollPos -= cyClient / cyChar ;
  5418.                             break ;
  5419.  
  5420.                        case SB_PAGEDOWN:
  5421.                             sVscrollPos += cyClient / cyChar ;
  5422.                             break ;
  5423.  
  5424.                        case SB_SLIDERPOSITION:
  5425.                             sVscrollPos = SHORT1FROMMP (mp2) ;
  5426.                             break ;
  5427.                        }
  5428.                   sVscrollPos = max (0, min (sVscrollPos, NUMLINES - 1)) ;
  5429.  
  5430.                   if (sVscrollPos != SHORT1FROMMR (WinSendMsg (hwndVscroll,
  5431.                                           SBM_QUERYPOS, NULL, NULL)))
  5432.                        {
  5433.                        WinSendMsg (hwndVscroll, SBM_SETPOS,
  5434.                                    MPFROMSHORT (sVscrollPos), NULL) ;
  5435.                        WinInvalidateRect (hwnd, NULL, FALSE) ;
  5436.                        }
  5437.                   return 0 ;
  5438.  
  5439.              case WM_PAINT:
  5440.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  5441.                   GpiErase (hps) ;
  5442.  
  5443.                   for (sLine = 0 ; sLine < NUMLINES ; sLine++)
  5444.                        {
  5445.                        ptl.x = cxCaps ;
  5446.                        ptl.y = cyClient - cyChar * (sLine + 1 - sVscrollPos)
  5447.                                         + cyDesc ;
  5448.  
  5449.                        GpiCharStringAt (hps, &ptl,
  5450.                                  (LONG) strlen (sysvals[sLine].szIdentifier),
  5451.                                  sysvals[sLine].szIdentifier) ;
  5452.  
  5453.                        ptl.x += 20 * cxCaps ;
  5454.                        GpiCharStringAt (hps, &ptl,
  5455.                                  (LONG) strlen (sysvals[sLine].szDescription),
  5456.                                  sysvals[sLine].szDescription) ;
  5457.  
  5458.                        ltoa (WinQuerySysValue (HWND_DESKTOP,
  5459.                                   sysvals[sLine].sIndex), szBuffer, 10) ;
  5460.  
  5461.                        ptl.x += 38 * cxChar ;
  5462.                        GpiCharStringAt (hps, &ptl, (LONG) strlen (szBuffer),
  5463.                                         szBuffer) ;
  5464.                        }
  5465.                   WinEndPaint (hps) ;
  5466.                   return 0 ;
  5467.              }
  5468.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  5469.         }
  5470.  
  5471.    The SYSVALS2.DEF File
  5472.  
  5473.    ;-------------------------------------
  5474.    ; SYSVALS2.DEF module definition file
  5475.    ;-------------------------------------
  5476.  
  5477.    NAME           SYSVALS2  WINDOWAPI
  5478.  
  5479.    DESCRIPTION    'System Values Display No. 2 (C) Charles Petzold, 1988'
  5480.    PROTMODE
  5481.    HEAPSIZE       1024
  5482.    STACKSIZE      8192
  5483.    EXPORTS        ClientWndProc
  5484.  
  5485.  
  5486.  The SYSVALS2 window with the vertical scroll bar is shown in Figure 4-11.
  5487.  
  5488.  The only change in main is that the flFrameFlags variable now includes the
  5489.  identifier FCF_VERTSCROLL. This causes the Presentation Manager to create a
  5490.  vertical scroll bar as part of the standard window. ClientWndProc contains
  5491.  two new variables: hwndVscroll, which stores the handle of the scroll-bar
  5492.  window, and sVscrollPos, which stores the current position of the scroll-bar
  5493.  slider.
  5494.  
  5495.  While processing the WM_CREATE message, the program obtains the window
  5496.  handle of the scroll bar:
  5497.  
  5498.    hwndVscroll = WinWindowFromID (
  5499.                        WinQueryWindow (hwnd, QW_PARENT, FALSE),
  5500.                        FID_VERTSCROLL) ;
  5501.  
  5502.  The program then initializes the range and slider position by sending the
  5503.  scroll bar a message:
  5504.  
  5505.    WinSendMsg (hwndVscroll, SBM_SETSCROLLBAR,
  5506.                        MPFROM2SHORT (sVscrollPos, 0),
  5507.                        MPFROM2SHORT (0, NUMLINES - 1)) ;
  5508.  
  5509.  The range (in mp2) is set to a minimum position of 0 and a maximum position
  5510.  of NUMLINES - 1. Thus the scroll bar has as many positions as there are
  5511.  lines of text. The initial value of sVscrollPos is 0 (because it is defined
  5512.  as a static variable but not explicitly initialized), so the slider is set
  5513.  to the topmost position.
  5514.  
  5515.  SYSVALS2 uses the position of the vertical scroll-bar slider to determine
  5516.  how it displays the lines of text in the client window. The value of the
  5517.  slider position corresponds to the line that appears at the top of the
  5518.  client window, as shown in the following table:
  5519.  
  5520.     Slider Position            Line at Top of Client Window
  5521.     0 (top)                    First
  5522.     1                          Second
  5523.     2                          Third
  5524.     ...                        ...
  5525.     NUMLINES - 1 (bottom)      Last
  5526.  
  5527.  The processing of the WM_VSCROLL message begins with the sVscrollPos
  5528.  variable being incremented or decremented, depending on the particular
  5529.  action of the mouse on the scroll bar:
  5530.  
  5531.    case WM_VSCROLL:
  5532.         switch (SHORT2FROMMP (mp2))
  5533.              {
  5534.              case SB_LINEUP:
  5535.                   sVscrollPos -= 1 ;
  5536.                   break ;
  5537.  
  5538.              case SB_LINEDOWN:
  5539.                   sVscrollPos += 1 ;
  5540.                   break ;
  5541.  
  5542.              case SB_PAGEUP:
  5543.                   sVscrollPos -= cyClient / cyChar ;
  5544.                   break ;
  5545.  
  5546.              case SB_PAGEDOWN:
  5547.                   sVscrollPos += cyClient / cyChar ;
  5548.                   break ;
  5549.  
  5550.              case SB_SLIDERPOSITION:
  5551.                   sVscrollPos = SHORT1FROMMP (mp2) ;
  5552.                   break ;
  5553.              }
  5554.  
  5555.    ■  For SB_LINEUP and SB_LINEDOWN, sVscrollPos is simply decremented or
  5556.       incremented by 1 for a change of one line.
  5557.  
  5558.    ■  For SB_PAGEUP and SB_PAGEDOWN, the variable is decreased or increased
  5559.       by cyClient / cyChar, which is the number of lines that can fit in the
  5560.       client window.
  5561.  
  5562.    ■  For the SB_SLIDERPOSITION action, the low USHORT encoded in mp2 is the
  5563.       new slider position after the slider has been dragged and released.
  5564.  
  5565.  SYSVALS2 ignores the SB_ENDSCROLL and SB_SLIDERTRACK actions.
  5566.  
  5567.  It's possible that the new value of sVscrollPos is outside the range of the
  5568.  scroll bar. For example, the scroll-bar slider could have been at the top of
  5569.  the scroll bar when the user clicked the up arrow. This statement uses the
  5570.  min and max macros defined in STDLIB.H to bring sVscrollPos within the
  5571.  scroll bar range:
  5572.  
  5573.    sVscrollPos = max (0, min (sVscrollPos, NUMLINES - 1)) ;
  5574.  
  5575.  After this adjustment, it's possible that sVscrollPos hasn't changed at all.
  5576.  To determine this, the value of sVscrollPos is checked against the real
  5577.  position of the slider, which is determined by sending an SBM_QUERYPOS
  5578.  message to the scroll-bar window:
  5579.  
  5580.    if (sVscrollPos != SHORT1FROMMR (WinSendMsg (hwndVscroll,
  5581.                                            SBM_QUERYPOS, NULL, NULL)))
  5582.         {
  5583.  
  5584.  If sVscrollPos has changed, then the slider is set to the new position by
  5585.  sending it the SBM_SETPOS message:
  5586.  
  5587.    WinSendMsg (hwndVscroll, SBM_SETPOS,
  5588.               MPFROMSHORT (sVscrollPos), NULL) ;
  5589.  
  5590.  Finally, SYSVALS2 must update its client window to reflect the change. It
  5591.  must get a presentation space handle, erase the entire client window,
  5592.  rewrite all the lines of text, and then release the presentation space
  5593.  handle. It does this by calling
  5594.  
  5595.    WinInvalidateRect (hwnd, NULL, FALSE) ;
  5596.  
  5597.  What's this? This one WinInvalidateRect statement does all that? It sure
  5598.  does, because this statement invalidates the entire client window and causes
  5599.  the Presentation Manager to post a WM_PAINT message in SYSVALS2's message
  5600.  queue. The repainting actually occurs during the WM_PAINT message.
  5601.  
  5602.  Earlier I discussed the idea of structuring your programs so that all
  5603.  drawing on the client window occurs during the WM_PAINT message. The
  5604.  WinInvalidateRect function is one of the tools that help you achieve this
  5605.  goal. The second parameter to WinInvalidateRect can be a pointer to a RECTL
  5606.  structure to specify that only a small rectangular area of the window is to
  5607.  be invalidated. Specifying NULL invalidates the whole window.
  5608.  
  5609.  WM_PAINT Processing in SYSVALS2
  5610.  
  5611.  Now let's look at the WM_PAINT processing. If you compare it with the
  5612.  WM_PAINT logic in SYSVALS1, you'll find only one changed statement. SYSVALS1
  5613.  used the following statement to set the y field of the POINTL structure
  5614.  passed to GpiCharStringAt:
  5615.  
  5616.    ptl.y = cyClient - cyChar * (sLine + 1) + cyDesc ;
  5617.  
  5618.  SYSVALS2, on the other hand, uses this statement:
  5619.  
  5620.    ptl.y = cyClient - cyChar * (sLine + 1 - sVscrollPos) + cyDesc ;
  5621.  
  5622.  When the scroll-bar slider is at the top of the bar, sVscrollPos is 0, and
  5623.  ptl.y is set to the same value as in SYSVALS1. The first line of text is
  5624.  displayed at the top of the client window. When sVscrollPos is 1, then ptl.y
  5625.  is set to (cyClient + cyDesc), which means that the first line of text is
  5626.  displayed right above the client window, which means that it isn't displayed
  5627.  at all. The second line of text (when sLine equals 1) occupies the top line
  5628.  of the client window. Thus SYSVALS2 calls GpiCharStringAt for all 48 lines
  5629.  of text, but the program begins writing these lines either at the top of the
  5630.  client window (when sVscrollPos is 0) or somewhere above the client window.
  5631.  The Presentation Manager obligingly clips everything that falls outside the
  5632.  window.
  5633.  
  5634.  This isn't very efficient WM_PAINT processing. It may not be too bad for 48
  5635.  lines of text, but what if there were several hundred lines? The painting
  5636.  should really be restricted only to what's needed. So let's not be satisfied
  5637.  that we got the program working. Anybody can do that. Let's take a crack at
  5638.  making it better.
  5639.  
  5640.  
  5641.  Optimizing the Code
  5642.  
  5643.  The new and improved SYSVALS3 program is displayed in Figure 4-12. In
  5644.  addition to faster vertical scroll-bar processing and repainting, this
  5645.  version also includes a horizontal scroll bar for left and right scrolling.
  5646.  The flFrameFlags variable in main includes the frame creation flag
  5647.  identifiers FCF_HORZSCROLL and FCF_VERTSCROLL.
  5648.  
  5649.  Figure 4-12.  The SYSVALS3 program.
  5650.  
  5651.    The SYSVALS3 File
  5652.  
  5653.    #--------------------
  5654.    # SYSVALS3 make file
  5655.    #--------------------
  5656.  
  5657.    sysvals3.obj : sysvals3.c sysvals.h
  5658.         cl -c -G2sw -W3 sysvals3.c
  5659.  
  5660.    sysvals3.exe : sysvals3.obj sysvals3.def
  5661.         link sysvals3, /align:16, NUL, os2, sysvals3
  5662.  
  5663.    The SYSVALS3.C File
  5664.  
  5665.    /*---------------------------------------------------
  5666.       SYSVALS3.C -- System Values Display Program No. 3
  5667.      ---------------------------------------------------*/
  5668.  
  5669.    #define INCL_WIN
  5670.    #define INCL_GPI
  5671.    #include <os2.h>
  5672.    #include <stdlib.h>
  5673.    #include <string.h>
  5674.    #include "sysvals.h"
  5675.  
  5676.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  5677.  
  5678.    int main (void)
  5679.         {
  5680.         static CHAR  szClientClass [] = "SysVals3" ;
  5681.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
  5682.                                     FCF_SIZEBORDER    | FCF_MINMAX   |
  5683.                                     FCF_SHELLPOSITION | FCF_TASKLIST |
  5684.                                     FCF_VERTSCROLL    | FCF_HORZSCROLL ;
  5685.         HAB          hab ;
  5686.         HMQ          hmq ;
  5687.         HWND         hwndFrame, hwndClient ;
  5688.         QMSG         qmsg ;
  5689.  
  5690.         hab = WinInitialize (0) ;
  5691.         hmq = WinCreateMsgQueue (hab, 0) ;
  5692.  
  5693.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  5694.  
  5695.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  5696.                                         &flFrameFlags, szClientClass, NULL,
  5697.                                         0L, NULL, 0, &hwndClient) ;
  5698.  
  5699.         WinSendMsg (hwndFrame, WM_SETICON,
  5700.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  5701.                     NULL) ;
  5702.  
  5703.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  5704.              WinDispatchMsg (hab, &qmsg) ;
  5705.  
  5706.         WinDestroyWindow (hwndFrame) ;
  5707.         WinDestroyMsgQueue (hmq) ;
  5708.         WinTerminate (hab) ;
  5709.         return 0 ;
  5710.         }
  5711.  
  5712.    LONG RtJustCharStringAt (HPS hps, POINTL *pptl, LONG lLength, CHAR *pchText
  5713.         {
  5714.         POINTL aptlTextBox[TXTBOX_COUNT] ;
  5715.  
  5716.         GpiQueryTextBox (hps, lLength, pchText, TXTBOX_COUNT, aptlTextBox) ;
  5717.  
  5718.         pptl->x -= aptlTextBox[TXTBOX_CONCAT].x ;
  5719.  
  5720.         return GpiCharStringAt (hps, pptl, lLength, pchText) ;
  5721.         }
  5722.  
  5723.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  5724.         {
  5725.         static HWND  hwndHscroll, hwndVscroll ;
  5726.         static SHORT sHscrollMax, sVscrollMax, sHscrollPos, sVscrollPos,
  5727.                      cxChar, cxCaps, cyChar, cyDesc, cxClient, cyClient,
  5728.                      cxTextTotal ;
  5729.         CHAR         szBuffer [10] ;
  5730.         FONTMETRICS  fm ;
  5731.         HPS          hps ;
  5732.         POINTL       ptl ;
  5733.         SHORT        sLine, sPaintBeg, sPaintEnd, sHscrollInc, sVscrollInc ;
  5734.         RECTL        rclInvalid ;
  5735.  
  5736.         switch (msg)
  5737.              {
  5738.              case WM_CREATE:
  5739.                   hps = WinGetPS (hwnd) ;
  5740.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  5741.  
  5742.                   cxChar = (SHORT) fm.lAveCharWidth ;
  5743.                   cxCaps = (SHORT) fm.lEmInc ;
  5744.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  5745.                   cyDesc = (SHORT) fm.lMaxDescender ;
  5746.  
  5747.                   WinReleasePS (hps) ;
  5748.  
  5749.                   cxTextTotal = 28 * cxCaps + 38 * cxChar ;
  5750.  
  5751.                   hwndHscroll = WinWindowFromID (
  5752.                                       WinQueryWindow (hwnd, QW_PARENT, FALSE),
  5753.                                       FID_HORZSCROLL) ;
  5754.  
  5755.                   hwndVscroll = WinWindowFromID (
  5756.                                       WinQueryWindow (hwnd, QW_PARENT, FALSE),
  5757.                                       FID_VERTSCROLL) ;
  5758.                   return 0 ;
  5759.  
  5760.              case WM_SIZE:
  5761.                   cxClient = SHORT1FROMMP (mp2) ;
  5762.                   cyClient = SHORT2FROMMP (mp2) ;
  5763.  
  5764.                   sHscrollMax = max (0, cxTextTotal - cxClient) ;
  5765.                   sHscrollPos = min (sHscrollPos, sHscrollMax) ;
  5766.  
  5767.                   WinSendMsg (hwndHscroll, SBM_SETSCROLLBAR,
  5768.                                            MPFROM2SHORT (sHscrollPos, 0),
  5769.                                            MPFROM2SHORT (0, sHscrollMax)) ;
  5770.  
  5771.                   WinEnableWindow (hwndHscroll, sHscrollMax ? TRUE : FALSE) ;
  5772.  
  5773.                   sVscrollMax = max (0, NUMLINES - cyClient / cyChar) ;
  5774.                   sVscrollPos = min (sVscrollPos, sVscrollMax) ;
  5775.  
  5776.                   WinSendMsg (hwndVscroll, SBM_SETSCROLLBAR,
  5777.                                            MPFROM2SHORT (sVscrollPos, 0),
  5778.                                            MPFROM2SHORT (0, sVscrollMax)) ;
  5779.  
  5780.                   WinEnableWindow (hwndVscroll, sVscrollMax ? TRUE : FALSE) ;
  5781.                   return 0 ;
  5782.  
  5783.              case WM_HSCROLL:
  5784.                   switch (SHORT2FROMMP (mp2))
  5785.                        {
  5786.                        case SB_LINELEFT:
  5787.                             sHscrollInc = -cxCaps ;
  5788.                             break ;
  5789.  
  5790.                        case SB_LINERIGHT:
  5791.                             sHscrollInc = cxCaps ;
  5792.                             break ;
  5793.  
  5794.                        case SB_PAGELEFT:
  5795.                             sHscrollInc = -8 * cxCaps ;
  5796.                             break ;
  5797.  
  5798.                        case SB_PAGERIGHT:
  5799.                             sHscrollInc = 8 * cxCaps ;
  5800.                             break ;
  5801.  
  5802.                        case SB_SLIDERPOSITION:
  5803.                             sHscrollInc = SHORT1FROMMP (mp2) - sHscrollPos;
  5804.                             break ;
  5805.  
  5806.                        default:
  5807.                             sHscrollInc = 0 ;
  5808.                             break ;
  5809.                        }
  5810.                   sHscrollInc = max (-sHscrollPos,
  5811.                                 min (sHscrollInc, sHscrollMax - sHscrollPos))
  5812.  
  5813.                   if (sHscrollInc != 0)
  5814.                        {
  5815.                        sHscrollPos += sHscrollInc ;
  5816.                        WinScrollWindow (hwnd, -sHscrollInc, 0,
  5817.                                       NULL, NULL, NULL, NULL, SW_INVALIDATERGN
  5818.  
  5819.                        WinSendMsg (hwndHscroll, SBM_SETPOS,
  5820.                                    MPFROMSHORT (sHscrollPos), NULL) ;
  5821.                        }
  5822.                   return 0 ;
  5823.  
  5824.              case WM_VSCROLL:
  5825.                   switch (SHORT2FROMMP (mp2))
  5826.                        {
  5827.                        case SB_LINEUP:
  5828.                             sVscrollInc = -1 ;
  5829.                             break ;
  5830.  
  5831.                        case SB_LINEDOWN:
  5832.                             sVscrollInc = 1 ;
  5833.                             break ;
  5834.  
  5835.                        case SB_PAGEUP:
  5836.                             sVscrollInc = min (-1, -cyClient / cyChar) ;
  5837.                             break ;
  5838.  
  5839.                        case SB_PAGEDOWN:
  5840.                             sVscrollInc = max (1, cyClient / cyChar) ;
  5841.                             break ;
  5842.  
  5843.                        case SB_SLIDERTRACK:
  5844.                             sVscrollInc = SHORT1FROMMP (mp2) - sVscrollPos;
  5845.                             break ;
  5846.  
  5847.                        default:
  5848.                             sVscrollInc = 0 ;
  5849.                             break ;
  5850.                        }
  5851.                   sVscrollInc = max (-sVscrollPos,
  5852.                                 min (sVscrollInc, sVscrollMax - sVscrollPos))
  5853.  
  5854.                   if (sVscrollInc != 0)
  5855.                        {
  5856.                        sVscrollPos += sVscrollInc ;
  5857.                        WinScrollWindow (hwnd, 0, cyChar * sVscrollInc,
  5858.                                       NULL, NULL, NULL, NULL, SW_INVALIDATERGN
  5859.  
  5860.                        WinSendMsg (hwndVscroll, SBM_SETPOS,
  5861.                                    MPFROMSHORT (sVscrollPos), NULL) ;
  5862.  
  5863.                        WinUpdateWindow (hwnd) ;
  5864.                        }
  5865.                   return 0 ;
  5866.  
  5867.              case WM_PAINT:
  5868.                   hps = WinBeginPaint (hwnd, NULL, &rclInvalid) ;
  5869.                   GpiErase (hps) ;
  5870.  
  5871.                   sPaintBeg = max (0, sVscrollPos +
  5872.                                  (cyClient - (SHORT) rclInvalid.yTop) / cyChar
  5873.                   sPaintEnd = min (NUMLINES, sVscrollPos +
  5874.                                  (cyClient - (SHORT) rclInvalid.yBottom)
  5875.                                       / cyChar + 1) ;
  5876.  
  5877.                   for (sLine = sPaintBeg ; sLine < sPaintEnd ; sLine++)
  5878.                        {
  5879.                        ptl.x = cxCaps - sHscrollPos ;
  5880.                        ptl.y = cyClient - cyChar * (sLine + 1 - sVscrollPos)
  5881.                                         + cyDesc ;
  5882.  
  5883.                        GpiCharStringAt (hps, &ptl,
  5884.                                  (LONG) strlen (sysvals[sLine].szIdentifier),
  5885.                                  sysvals[sLine].szIdentifier) ;
  5886.  
  5887.                        ptl.x += 20 * cxCaps ;
  5888.                        GpiCharStringAt (hps, &ptl,
  5889.                                  (LONG) strlen (sysvals[sLine].szDescription),
  5890.                                  sysvals[sLine].szDescription) ;
  5891.  
  5892.                        ltoa (WinQuerySysValue (HWND_DESKTOP,
  5893.                                   sysvals[sLine].sIndex), szBuffer, 10) ;
  5894.  
  5895.                        ptl.x += 38 * cxChar + 6 * cxCaps ;
  5896.                        RtJustCharStringAt (hps, &ptl, (LONG) strlen (szBuffer)
  5897.                                            szBuffer) ;
  5898.                        }
  5899.                   WinEndPaint (hps) ;
  5900.                   return 0 ;
  5901.              }
  5902.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  5903.         }
  5904.  
  5905.    The SYSVALS3.DEF File
  5906.  
  5907.    ;-------------------------------------
  5908.    ; SYSVALS3.DEF module definition file
  5909.    ;-------------------------------------
  5910.  
  5911.    NAME           SYSVALS3  WINDOWAPI
  5912.  
  5913.    DESCRIPTION    'System Values Display No. 3 (C) Charles Petzold, 1988'
  5914.    PROTMODE
  5915.    HEAPSIZE       1024
  5916.    STACKSIZE      8192
  5917.    EXPORTS        ClientWndProc
  5918.  
  5919.  
  5920.  The SYSVALS3 window is shown in Figure 4-13.
  5921.  
  5922.  Right-justified Text
  5923.  
  5924.  You'll notice I've also prettied up the display a little. In SYSVALS1 and
  5925.  SYSVALS2, the values returned from WinQuerySysValue were displayed beginning
  5926.  at the same horizontal pixel position. Columns of numbers are commonly
  5927.  displayed right justified. In SYSVALS3, the RtJustCharStringAt function
  5928.  results in right-justified text.
  5929.  
  5930.    LONG RtJustCharStringAt (HPS hps, POINTL *pptl, LONG lLength, CHAR *pchText
  5931.         {
  5932.         POINTL aptlTextBox[TXTBOX_COUNT] ;
  5933.         GpiQueryTextBox (hps, lLength, pchText, TXTBOX_COUNT, aptlTextBox) ;
  5934.         pptl->x -= aptlTextBox[TXTBOX_CONCAT].x ;
  5935.         return GpiCharStringAt (hps, pptl, lLength, pchText) ;
  5936.         }
  5937.  
  5938.  This function is defined with the same parameters as GpiCharStringAt, but
  5939.  when the function is called, the x field of the POINTL structure should be
  5940.  set to the pixel position where the text should end rather than begin. This
  5941.  function uses the identifiers TXTBOX_COUNT and TXTBOX_CONCAT, defined in
  5942.  PMGPI.H. They are used when working with the GpiQueryTextBox function, which
  5943.  obtains an array of POINTL structures that give the coordinates of the four
  5944.  corners of a text string, assuming that the text begins at the point (0,0).
  5945.  The TXTBOX_CONCAT element of the aptl structure contains the coordinates of
  5946.  the end of the string (where more text would follow). So when the x
  5947.  coordinate of TXTBOX_CONCAT is subtracted from the x field of the POINTL
  5948.  structure passed to RtJustCharStringAt, the resulting value is the x
  5949.  coordinate that will result in right-justified text.
  5950.  
  5951.  Changing the Range Based on Window Size
  5952.  
  5953.  Another change incorporated in SYSVALS3 is that the scroll-bar range and
  5954.  slider position are no longer set during processing of the WM_CREATE
  5955.  message. Instead, a new range and position are set during each WM_SIZE
  5956.  message.
  5957.  
  5958.  The primary goal is to have the last line of text be visible at the bottom
  5959.  of the client window. So during the WM_SIZE message, the maximum position of
  5960.  the vertical scroll-bar slider is calculated based on the total number of
  5961.  text lines and the number of lines that can fit in the client window:
  5962.  
  5963.    sVscrollMax = max (0, NUMLINES - cyClient / cyChar) ;
  5964.  
  5965.  The existing value of sVscrollPos could be outside this new range, so
  5966.  sVscrollPos is adjusted using the min macro:
  5967.  
  5968.    sVscrollPos = min (sVscrollPos, sVscrollMax) ;
  5969.  
  5970.  Then the new range and position are set by sending the scroll bar a message:
  5971.  
  5972.    WinSendMsg (hwndVscroll, SBM_SETSCROLLBAR,
  5973.                             MPFROM2SHORT (sVscrollPos, 0),
  5974.                             MPFROM2SHORT (0, sVscrollMax)) ;
  5975.  
  5976.  If all the text fits in the client window, then sVscrollMax equals 0, and
  5977.  there is no need for a working scroll bar. To enable or disable the scroll
  5978.  bar, call WinEnableWindow based on the value of sVscrollMax:
  5979.  
  5980.    WinEnableWindow (hwndVscroll, sVscrollMax ? TRUE : FALSE) ;
  5981.  
  5982.  A disabled scroll bar is made partly invisible and beeps if you click on it.
  5983.  
  5984.  Scrolling the Window
  5985.  
  5986.  Rather than immediately altering the value of sVscrollPos, the new
  5987.  WM_VSCROLL processing sets a variable named sVscrollInc to the incremental
  5988.  change in the slider position indicated by the mouse action:
  5989.  
  5990.    case WM_VSCROLL:
  5991.         switch (SHORT2FROMMP (mp2))
  5992.              {
  5993.              case SB_LINEUP:
  5994.                   sVscrollInc = -1 ;
  5995.                   break ;
  5996.  
  5997.              case SB_LINEDOWN:
  5998.                   sVscrollInc = 1 ;
  5999.                   break ;
  6000.  
  6001.              case SB_PAGEUP:
  6002.                   sVscrollInc = min (-1, -cyClient / cyChar) ;
  6003.                   break ;
  6004.  
  6005.              case SB_PAGEDOWN:
  6006.                   sVscrollInc = max (1, cyClient / cyChar) ;
  6007.                   break ;
  6008.  
  6009.              case SB_SLIDERTRACK:
  6010.                   sVscrollInc = SHORT1FROMMP (mp2) - sVscrollPos;
  6011.                   break ;
  6012.  
  6013.              default:
  6014.                   sVscrollInc = 0 ;
  6015.                   break ;
  6016.              }
  6017.  
  6018.  SYSVALS3 processes the SB_SLIDERTRACK action rather than SB_SLIDERPOSITION.
  6019.  This allows the program to change the client window while the user is
  6020.  dragging the slider with the mouse rather than after the dragging action is
  6021.  completed.
  6022.  
  6023.  Next, sVscrollInc is adjusted based on the position of the slider and the
  6024.  range maximum:
  6025.  
  6026.    iVscrollInc = max (-sVscrollPos,
  6027.                   min (sVscrollInc, sVscrollMax - sVscrollPos));
  6028.  
  6029.  If sVscrollInc is still nonzero, processing continues with the calculation
  6030.  of a new slider position:
  6031.  
  6032.    sVscrollPos += sVscrollInc ;
  6033.  
  6034.  In SYSVALS2, the entire window was redrawn whenever the scroll-bar position
  6035.  was changed. SYSVALS3 attempts to preserve part of the window by scrolling
  6036.  the contents of the window:
  6037.  
  6038.    WinScrollWindow (hwnd, 0, cyChar * sVscrollInc,
  6039.                   NULL, NULL, NULL, NULL, SW_INVALIDATERGN) ;
  6040.  
  6041.  This function can scroll a rectangular area of a window up, down, left, or
  6042.  right. Here we're specifying that the contents of the entire window move up
  6043.  by cyChar * sVscrollInc pixels. Thus, if the action is SB_LINEDOWN, then
  6044.  sVscrollInc is 1, and the contents of the window move up cyChar pixels. This
  6045.  means that only the last line at the bottom of the window has to be redrawn.
  6046.  Including SW_INVALIDATERGN as the last parameter of WinScrollWindow tells
  6047.  the Presentation Manager to invalidate the area uncovered by the scroll──
  6048.  the bottom line of the client window. A WM_PAINT message is placed in
  6049.  SYSVALS3's message queue.
  6050.  
  6051.  Normally, SYSVALS3 would retrieve the WM_PAINT message from its message
  6052.  queue and repaint the window. If the scroll bar is busy receiving and
  6053.  processing mouse messages, however, this won't happen immediately. We can
  6054.  force the client window to be repainted right away with this function:
  6055.  
  6056.    WinUpdateWindow (hwnd) ;
  6057.  
  6058.  This causes the Presentation Manager to call ClientWndProc with the WM_PAINT
  6059.  message.
  6060.  
  6061.  Painting Only the Invalid Rectangle
  6062.  
  6063.  When the window procedure receives a WM_PAINT message, it's likely that only
  6064.  a small rectangular part of the client window is invalid and needs to be
  6065.  repainted. When a program obtains a presentation space handle from
  6066.  WinBeginPaint, it can paint only within that rectangular invalid area. The
  6067.  Presentation Manager must clip all screen output that falls outside the
  6068.  invalid area. But for optimum efficiency, the program itself shouldn't make
  6069.  any GPI calls that will eventually be ignored by the Presentation Manager.
  6070.  
  6071.  To speed up the painting, SYSVALS3 obtains the coordinates of the
  6072.  rectangular invalid area. It does this in the WinBeginPaint function:
  6073.  
  6074.    hps = WinBeginPaint (hwnd, NULL, &rclInvalid) ;
  6075.  
  6076.  The Presentation Manager fills in the fields of the RECTL structure named
  6077.  rclInvalid with the coordinates of this rectangle.
  6078.  
  6079.  SYSVALS3 then uses the yTop and yBottom fields of the RECTL structure to
  6080.  determine the range of lines that must be repainted:
  6081.  
  6082.    sPaintBeg = max (0, sVscrollPos +
  6083.                   (cyClient - (SHORT) rclInvalid.yTop) / cyChar) ;
  6084.    sPaintEnd = min (NUMLINES, sVscrollPos +
  6085.                   (cyClient - (SHORT) rclInvalid.yBottom) / cyChar + 1) ;
  6086.  
  6087.  The for loop encompasses only this range:
  6088.  
  6089.    for (sLine = sPaintBeg ; sLine < sPaintEnd ; sLine++)
  6090.  
  6091.  The improved efficiency in processing the WM_VSCROLL and WM_PAINT messages
  6092.  allows SYSVALS3 to move the contents of the window during SB_SLIDERTRACK
  6093.  actions from the vertical scroll bar.
  6094.  
  6095.  
  6096.  Adding a Keyboard Interface
  6097.  
  6098.  Of course, if you don't have a mouse, you haven't been able to scroll
  6099.  SYSVALS2 or SYSVALS3 at all. So let's make one final change to the program
  6100.  to allow the mouseless among us to scroll the window using the cursor
  6101.  movement keys.
  6102.  
  6103.  Scroll bars understand keyboard messages. However, the Presentation Manager
  6104.  posts keyboard messages to only one window──the window with the "input
  6105.  focus" (as you'll see in Chapter 8, when we examine the keyboard in more
  6106.  depth). If your program is active, then the window with the input focus is
  6107.  generally the client window rather than the scroll-bar window.
  6108.  
  6109.  Earlier I mentioned that the frame window is the initial recipient of
  6110.  notification messages from the scroll bar and that the frame window sends
  6111.  these messages to the client window. This raises an interesting question: If
  6112.  the frame window passes scroll bar messages to the client window, why can't
  6113.  the client window pass keyboard messages to the scroll-bar window? Let's do
  6114.  it.
  6115.  
  6116.  The keyboard message is called WM_CHAR. For the cursor movement keys, the
  6117.  high USHORT of mp2 is a "virtual key code" (more on this in Chapter 5) that
  6118.  identifies the key. The PMWIN.H header file has a macro called CHARMSG that
  6119.  lets you extract this code. You probably want the Up Arrow, Down Arrow, Page
  6120.  Up, and Page Down keys to control the vertical scroll bar and the Left Arrow
  6121.  and Right Arrow keys to control the horizontal scroll bar. Here's the code
  6122.  to be added to the window procedure:
  6123.  
  6124.    case WM_CHAR:
  6125.         switch (CHARMSG (&msg) ->vkey
  6126.              {
  6127.              case VK_LEFT:
  6128.              case VK_RIGHT:
  6129.                   return WinSendMsg (hwndHscroll, msg, mp1, mp2) ;
  6130.              case VK_UP:
  6131.              case VK_DOWN:
  6132.              case VK_PAGEUP:
  6133.              case VK_PAGEDOWN:
  6134.                   return WinSendMsg (hwndVscroll, msg, mp1, mp2) ;
  6135.              }
  6136.         break ;
  6137.  
  6138.  Simple enough, wouldn't you say? With this addition, I declare the program
  6139.  finished. The name of the final version is simply SYSVALS without any
  6140.  degrading numeric suffix; the program is shown in Figure 4-14.
  6141.  
  6142.  Figure 4-14.  The final SYSVALS program.
  6143.  
  6144.    The SYSVALS File
  6145.  
  6146.    #--------------------
  6147.    # SYSVALS make file
  6148.    #--------------------
  6149.  
  6150.    sysvals.obj : sysvals.c sysvals.h
  6151.         cl -c -G2sw -W3 sysvals.c
  6152.  
  6153.    sysvals.exe : sysvals.obj sysvals.def
  6154.         link sysvals, /align:16, NUL, os2, sysvals
  6155.  
  6156.    The SYSVALS.C File
  6157.  
  6158.    /*--------------------------------------------
  6159.       SYSVALS.C -- System Values Display Program
  6160.      --------------------------------------------*/
  6161.  
  6162.    #define INCL_WIN
  6163.    #define INCL_GPI
  6164.    #include <os2.h>
  6165.    #include <stdlib.h>
  6166.    #include <string.h>
  6167.    #include "sysvals.h"
  6168.  
  6169.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  6170.  
  6171.    int main (void)
  6172.         {
  6173.         static CHAR  szClientClass [] = "SysVals" ;
  6174.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
  6175.                                     FCF_SIZEBORDER    | FCF_MINMAX   |
  6176.                                     FCF_SHELLPOSITION | FCF_TASKLIST |
  6177.                                     FCF_VERTSCROLL    | FCF_HORZSCROLL ;
  6178.         HAB            hab ;
  6179.         HMQ            hmq ;
  6180.         HWND           hwndFrame, hwndClient ;
  6181.         QMSG           qmsg ;
  6182.  
  6183.         hab = WinInitialize (0) ;
  6184.         hmq = WinCreateMsgQueue (hab, 0) ;
  6185.  
  6186.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  6187.  
  6188.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  6189.                                         &flFrameFlags, szClientClass, NULL,
  6190.                                         0L, NULL, 0, &hwndClient) ;
  6191.  
  6192.         WinSendMsg (hwndFrame, WM_SETICON,
  6193.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  6194.                     NULL) ;
  6195.  
  6196.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  6197.              WinDispatchMsg (hab, &qmsg) ;
  6198.  
  6199.         WinDestroyWindow (hwndFrame) ;
  6200.         WinDestroyMsgQueue (hmq) ;
  6201.         WinTerminate (hab) ;
  6202.         return 0 ;
  6203.         }
  6204.  
  6205.    LONG RtJustCharStringAt (HPS hps, POINTL *pptl, LONG lLength, CHAR *pchText
  6206.         {
  6207.         POINTL aptlTextBox[TXTBOX_COUNT] ;
  6208.  
  6209.         GpiQueryTextBox (hps, lLength, pchText, TXTBOX_COUNT, aptlTextBox) ;
  6210.  
  6211.         pptl->x -= aptlTextBox[TXTBOX_CONCAT].x ;
  6212.  
  6213.         return GpiCharStringAt (hps, pptl, lLength, pchText) ;
  6214.         }
  6215.  
  6216.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  6217.         {
  6218.         static HWND  hwndHscroll, hwndVscroll ;
  6219.         static SHORT sHscrollMax, sVscrollMax, sHscrollPos, sVscrollPos,
  6220.                      cxChar, cxCaps, cyChar, cyDesc, cxClient, cyClient,
  6221.                      cxTextTotal ;
  6222.         CHAR         szBuffer [10] ;
  6223.         FONTMETRICS  fm ;
  6224.         HPS          hps ;
  6225.         POINTL       ptl ;
  6226.         SHORT        sLine, sPaintBeg, sPaintEnd, sHscrollInc, sVscrollInc ;
  6227.         RECTL        rclInvalid ;
  6228.  
  6229.         switch (msg)
  6230.              {
  6231.              case WM_CREATE:
  6232.                   hps = WinGetPS (hwnd) ;
  6233.  
  6234.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  6235.  
  6236.                   cxChar = (SHORT) fm.lAveCharWidth ;
  6237.                   cxCaps = (SHORT) fm.lEmInc ;
  6238.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  6239.                   cyDesc = (SHORT) fm.lMaxDescender ;
  6240.  
  6241.                   WinReleasePS (hps) ;
  6242.  
  6243.                   cxTextTotal = 28 * cxCaps + 38 * cxChar ;
  6244.  
  6245.                   hwndHscroll = WinWindowFromID (
  6246.                                       WinQueryWindow (hwnd, QW_PARENT, FALSE),
  6247.                                       FID_HORZSCROLL) ;
  6248.  
  6249.                   hwndVscroll = WinWindowFromID (
  6250.                                       WinQueryWindow (hwnd, QW_PARENT, FALSE),
  6251.                                       FID_VERTSCROLL) ;
  6252.                   return 0 ;
  6253.  
  6254.              case WM_SIZE:
  6255.                   cxClient = SHORT1FROMMP (mp2) ;
  6256.                   cyClient = SHORT2FROMMP (mp2) ;
  6257.  
  6258.                   sHscrollMax = max (0, cxTextTotal - cxClient) ;
  6259.                   sHscrollPos = min (sHscrollPos, sHscrollMax) ;
  6260.  
  6261.                   WinSendMsg (hwndHscroll, SBM_SETSCROLLBAR,
  6262.                                            MPFROM2SHORT (sHscrollPos, 0),
  6263.                                            MPFROM2SHORT (0, sHscrollMax)) ;
  6264.  
  6265.                   WinEnableWindow (hwndHscroll, sHscrollMax ? TRUE : FALSE) ;
  6266.  
  6267.                   sVscrollMax = max (0, NUMLINES - cyClient / cyChar) ;
  6268.                   sVscrollPos = min (sVscrollPos, sVscrollMax) ;
  6269.  
  6270.                   WinSendMsg (hwndVscroll, SBM_SETSCROLLBAR,
  6271.                                            MPFROM2SHORT (sVscrollPos, 0),
  6272.                                            MPFROM2SHORT (0, sVscrollMax)) ;
  6273.  
  6274.                   WinEnableWindow (hwndVscroll, sVscrollMax ? TRUE : FALSE) ;
  6275.                   return 0 ;
  6276.  
  6277.              case WM_HSCROLL:
  6278.                   switch (SHORT2FROMMP (mp2))
  6279.                        {
  6280.                        case SB_LINELEFT:
  6281.                             sHscrollInc = -cxCaps ;
  6282.                             break ;
  6283.  
  6284.                        case SB_LINERIGHT:
  6285.                             sHscrollInc = cxCaps ;
  6286.                             break ;
  6287.  
  6288.                        case SB_PAGELEFT:
  6289.                             sHscrollInc = -8 * cxCaps ;
  6290.                             break ;
  6291.  
  6292.                        case SB_PAGERIGHT:
  6293.                             sHscrollInc = 8 * cxCaps ;
  6294.                             break ;
  6295.  
  6296.                        case SB_SLIDERPOSITION:
  6297.                             sHscrollInc = SHORT1FROMMP (mp2) - sHscrollPos;
  6298.                             break ;
  6299.  
  6300.                        default:
  6301.                             sHscrollInc = 0 ;
  6302.                             break ;
  6303.                        }
  6304.  
  6305.                   sHscrollInc = max (-sHscrollPos,
  6306.                                 min (sHscrollInc, sHscrollMax - sHscrollPos))
  6307.  
  6308.                   if (sHscrollInc != 0)
  6309.                        {
  6310.                        sHscrollPos += sHscrollInc ;
  6311.                        WinScrollWindow (hwnd, -sHscrollInc, 0,
  6312.                                       NULL, NULL, NULL, NULL, SW_INVALIDATERGN
  6313.  
  6314.                        WinSendMsg (hwndHscroll, SBM_SETPOS,
  6315.                                    MPFROMSHORT (sHscrollPos), NULL) ;
  6316.                        }
  6317.                   return 0 ;
  6318.  
  6319.              case WM_VSCROLL:
  6320.                   switch (SHORT2FROMMP (mp2))
  6321.                        {
  6322.                        case SB_LINEUP:
  6323.                             sVscrollInc = -1 ;
  6324.                             break ;
  6325.  
  6326.                        case SB_LINEDOWN:
  6327.                             sVscrollInc = 1 ;
  6328.                             break ;
  6329.  
  6330.                        case SB_PAGEUP:
  6331.                             sVscrollInc = min (-1, -cyClient / cyChar) ;
  6332.                             break ;
  6333.  
  6334.                        case SB_PAGEDOWN:
  6335.                             sVscrollInc = max (1, cyClient / cyChar) ;
  6336.                             break ;
  6337.  
  6338.                        case SB_SLIDERTRACK:
  6339.                             sVscrollInc = SHORT1FROMMP (mp2) - sVscrollPos;
  6340.                             break ;
  6341.  
  6342.                        default:
  6343.                             sVscrollInc = 0 ;
  6344.                             break ;
  6345.                        }
  6346.  
  6347.                   sVscrollInc = max (-sVscrollPos,
  6348.                                 min (sVscrollInc, sVscrollMax - sVscrollPos))
  6349.  
  6350.                   if (sVscrollInc != 0) ;
  6351.                        {
  6352.                        sVscrollPos += sVscrollInc ;
  6353.                        WinScrollWindow (hwnd, 0, cyChar * sVscrollInc,
  6354.                                       NULL, NULL, NULL, NULL, SW_INVALIDATERGN
  6355.  
  6356.                        WinSendMsg (hwndVscroll, SBM_SETPOS,
  6357.                                    MPFROMSHORT (sVscrollPos), NULL) ;
  6358.                        WinUpdateWindow (hwnd) ;
  6359.                        }
  6360.                   return 0 ;
  6361.  
  6362.              case WM_CHAR:
  6363.                   switch (CHARMSG(&msg)->vkey)
  6364.                        {
  6365.                        case VK_LEFT:
  6366.                        case VK_RIGHT:
  6367.                             return WinSendMsg (hwndHscroll, msg, mp1, mp2) ;
  6368.                        case VK_UP:
  6369.                        case VK_DOWN:
  6370.                        case VK_PAGEUP:
  6371.                        case VK_PAGEDOWN:
  6372.                             return WinSendMsg (hwndVscroll, msg, mp1, mp2) ;
  6373.                        }
  6374.                   break ;
  6375.  
  6376.              case WM_PAINT:
  6377.                   hps = WinBeginPaint (hwnd, NULL, &rclInvalid) ;
  6378.                   GpiErase (hps) ;
  6379.  
  6380.                   sPaintBeg = max (0, sVscrollPos +
  6381.                                  (cyClient - (SHORT) rclInvalid.yTop) / cyChar
  6382.                   sPaintEnd = min (NUMLINES, sVscrollPos +
  6383.                                  (cyClient - (SHORT) rclInvalid.yBottom)
  6384.                                       / cyChar + 1) ;
  6385.  
  6386.                   for (sLine = sPaintBeg ; sLine < sPaintEnd ; sLine++)
  6387.                        {
  6388.                        ptl.x = cxCaps - sHscrollPos ;
  6389.                        ptl.y = cyClient - cyChar * (sLine + 1 - sVscrollPos)
  6390.                                         + cyDesc ;
  6391.  
  6392.                        GpiCharStringAt (hps, &ptl,
  6393.                                  (LONG) strlen (sysvals[sLine].szIdentifier),
  6394.                                  sysvals[sLine].szIdentifier) ;
  6395.  
  6396.                        ptl.x += 20 * cxCaps ;
  6397.                        GpiCharStringAt (hps, &ptl,
  6398.                                  (LONG) strlen (sysvals[sLine].szDescription),
  6399.                                  sysvals[sLine].szDescription) ;
  6400.  
  6401.                        ltoa (WinQuerySysValue (HWND_DESKTOP,
  6402.                                   sysvals[sLine].sIndex), szBuffer, 10) ;
  6403.  
  6404.                        ptl.x += 38 * cxChar + 6 * cxCaps ;
  6405.                        RtJustCharStringAt (hps, &ptl, (LONG) strlen (szBuffer)
  6406.                                            szBuffer) ;
  6407.                        }
  6408.  
  6409.                   WinEndPaint (hps) ;
  6410.                   return 0 ;
  6411.              }
  6412.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  6413.         }
  6414.  
  6415.    The SYSVALS.DEF File
  6416.  
  6417.    ;------------------------------------
  6418.    ; SYSVALS.DEF module definition file
  6419.    ;------------------------------------
  6420.  
  6421.    NAME           SYSVALS   WINDOWAPI
  6422.  
  6423.    DESCRIPTION    'System Values Display (C) Charles Petzold, 1988'
  6424.    PROTMODE
  6425.    HEAPSIZE       1024
  6426.    STACKSIZE      8192
  6427.    EXPORTS        ClientWndProc
  6428.  
  6429.  
  6430.  Chapter 5  The Five GPI Primitives
  6431.  ───────────────────────────────────────────────────────────────────────────
  6432.  
  6433.  
  6434.  The world of computer graphics is often separated into two large categories:
  6435.  "raster" graphics and "vector" graphics. These terms refer both to graphics
  6436.  output devices and to the way that an application program draws graphics
  6437.  objects on these devices. Raster output devices display images that are made
  6438.  up of dots called pixels or "pels" (picture elements). Video displays,
  6439.  dot-matrix printers, and laser printers are all raster devices. Vector
  6440.  output devices──such as plotters──display images made up of lines and
  6441.  filled areas.
  6442.  
  6443.  ───────────────────────────────────────────────────────────────────────────
  6444.  NOTE:
  6445.     The distinction between raster and vector devices gets a little fuzzy
  6446.     with devices such as the IBM 8514/A video display adapter and PostScript
  6447.     laser printers: Although these devices are technically raster devices,
  6448.     they contain a high-level graphics interface that understands and
  6449.     interprets vector drawing commands.
  6450.  ───────────────────────────────────────────────────────────────────────────
  6451.  
  6452.  The OS/2 Graphics Programming Interface (one of the two major components of
  6453.  the Presentation Manager, the other being the windowing and user interface)
  6454.  is fundamentally, but not exclusively, a vector graphics system:
  6455.  Presentation Manager programs draw graphics in terms of lines and filled
  6456.  areas. This approach works for every type of graphics output device──the
  6457.  drawing commands need only be translated by a device driver into a format
  6458.  the device can understand: vector drawing commands for vector output devices
  6459.  and pixels for raster devices.
  6460.  
  6461.  GPI also has several functions for working with raster graphics. These
  6462.  functions allow a program to draw individual pixels (or, more commonly,
  6463.  collections of pixels called "bitmaps") on an output device. However, these
  6464.  functions are useful only with raster devices: Vector devices cannot
  6465.  adequately draw individual dots.
  6466.  
  6467.  Vectors and rasters each have their place in the world of graphics. An
  6468.  architectural drawing is obviously a job for vector graphics, whereas the
  6469.  reproduction of a digitized photograph requires raster graphics.
  6470.  Accordingly, vector and raster graphics each have their place in this book:
  6471.  I cover vector graphics in this chapter and raster graphics in the next
  6472.  chapter.
  6473.  
  6474.  The following sections describe the five GPI primitives that form the basis
  6475.  of the GPI vector graphics system: lines, patterned areas, text, marker
  6476.  symbols, and images.
  6477.  
  6478.  
  6479.  GPI Primitive 1: Lines
  6480.  
  6481.  When drawing text in Chapter 4 we specified the starting point of a text
  6482.  string using a POINTL structure. You also use the POINTL structure for
  6483.  drawing lines. POINTL is defined in OS2DEF.H like this:
  6484.  
  6485.    typedef struct _POINTL
  6486.         {
  6487.         LONG x ;
  6488.         LONG y ;
  6489.         }
  6490.         POINTL ;
  6491.  
  6492.  The two fields x and y define a point in terms of GPI coordinates. For a
  6493.  cached micro-PS, these coordinates are in units of pixels relative to the
  6494.  lower-left corner of the presentation space, which corresponds to the
  6495.  lower-left corner of the window. For convenience, I'll sometimes use the
  6496.  notation (x,y) to refer to a point in the presentation space. The point
  6497.  (0,0) is the lower-left corner of the window. The x (horizontal) coordinates
  6498.  increase to the right and the y (vertical) coordinates increase going up.
  6499.  
  6500.  A structure variable of type POINTL is usually given a prefix of ptl. If you
  6501.  need only one POINTL structure variable, you can name it ptl and define it
  6502.  like this:
  6503.  
  6504.    POINTL ptl ;
  6505.  
  6506.  You can define an array of POINTL structures like this:
  6507.  
  6508.    POINTL aptl[5] ;
  6509.  
  6510.  and define a pointer to a POINTL structure like this:
  6511.  
  6512.    POINTL *pptl ;
  6513.  
  6514.  Simple Straight Lines
  6515.  
  6516.  To draw a straight line, you must specify the two points that indicate the
  6517.  beginning and end of the line. Let's assume that cxClient and cyClient have
  6518.  been set to the width and height of the client window. Suppose you want to
  6519.  draw a diagonal line from the upper-left corner of the client window to the
  6520.  lower-right corner.
  6521.  
  6522.  After obtaining a handle to a cached micro-PS from the WinGetPS or
  6523.  WinBeginPaint function, you set the two fields of a POINTL structure to the
  6524.  beginning of the line: the point (0,cyClient). You then call GpiMove:
  6525.  
  6526.    ptl.x = 0 ;
  6527.    ptl.y = cyClient ;
  6528.    GpiMove (hps, &ptl) ;
  6529.  
  6530.  GpiMove does not draw anything. Instead, it sets the "current position"
  6531.  (defined shortly) to the specified point.
  6532.  
  6533.  You then set the two fields of the structure to the second point and call
  6534.  GpiLine:
  6535.  
  6536.    ptl.x = cxClient ;
  6537.    ptl.y = 0 ;
  6538.    GpiLine (hps, &ptl) ;
  6539.  
  6540.  GpiLine draws the line from (0,cyClient) to (cxClient,0).
  6541.  
  6542.  Initially, it may seem annoying that drawing a single line requires four
  6543.  assignment statements and two function calls. The syntax of the GpiMove and
  6544.  GpiLine functions is defined in this way to be consistent with the
  6545.  GpiPolyLine and GpiQueryCurrentPosition functions discussed later in this
  6546.  chapter. In actual practice, it's usually not as inconvenient as it first
  6547.  appears to be.
  6548.  
  6549.  The Current Position
  6550.  
  6551.  We've just seen how the GpiMove function does not draw anything itself.
  6552.  Instead, it affects the operation of a subsequent call to GpiLine. The
  6553.  GpiMove function is said to set an "attribute" of the presentation space. In
  6554.  one sense, the presentation space is simply a data structure internal to
  6555.  GPI. This data structure identifies the output device associated with the
  6556.  presentation space and also retains all the attributes of the presentation
  6557.  space.
  6558.  
  6559.  The GpiMove function sets the current position to the point specified in the
  6560.  function. The current position is used by most GPI drawing functions as a
  6561.  starting position when drawing a graphics object such as a line.
  6562.  
  6563.  When you first obtain a handle to a cached micro-PS by calling WinGetPS or
  6564.  WinBeginPaint, all the attributes are set to default values. The default
  6565.  current position is the point (0,0). When you release a presentation space
  6566.  handle by calling WinReleasePS or WinEndPaint, any changes you've made to
  6567.  the attributes are lost.
  6568.  
  6569.  The GpiLine function uses the current position as a starting point for the
  6570.  line it draws and then sets the current position to the end of the line──
  6571.  the point specified in the GpiLine function. Thus, you can draw another line
  6572.  connected to the first by calling GpiLine again with a new point.
  6573.  
  6574.  For example, suppose you want to draw a big "V" in your client window. This
  6575.  job requires just one call to GpiMove and two calls to GpiLine:
  6576.  
  6577.    ptl.x = 0 ;
  6578.    ptl.y = cyClient ;
  6579.    GpiMove (hps, &ptl) ;
  6580.  
  6581.    ptl.x = cxClient / 2 ;
  6582.    ptl.y = 0 ;
  6583.    GpiLine (hps, &ptl) ;
  6584.  
  6585.    ptl.x = cxClient ;
  6586.    ptl.y = cyClient ;
  6587.    GpiLine (hps, &ptl) ;
  6588.  
  6589.  If you enjoy typing long function names, you can use the
  6590.  GpiSetCurrentPosition function rather than GpiMove:
  6591.  
  6592.    GpiSetCurrentPosition (hps, &ptl) ;
  6593.  
  6594.  When using a cached micro-PS there is no difference between GpiMove and
  6595.  GpiSetCurrentPosition. You can also obtain the current position by using
  6596.  this function:
  6597.  
  6598.    GpiQueryCurrentPosition (hps, &ptl) ;
  6599.  
  6600.  Note that all four functions covered have had the same parameter syntax.
  6601.  
  6602.  Some graphics programming languages have a function that draws a line from
  6603.  the current position to a point relative to the current position. GPI does
  6604.  not include such a function, but it's easy enough to write one:
  6605.  
  6606.    LONG LineRelative (HPS hps, POINTL *pptlRelative)
  6607.         {
  6608.         POINTL ptl ;
  6609.  
  6610.         GpiQueryCurrentPosition (hps, &ptl) ;
  6611.         ptl.x += pptlRelative->x ;
  6612.         ptl.y += pptlRelative->y ;
  6613.         return GpiLine (hps, &ptl) ;
  6614.         }
  6615.  
  6616.  The pptl prefix of pptlRelative stands for "pointer to a POINTL structure."
  6617.  
  6618.  Throughout this chapter, we will work mostly with three types of GPI
  6619.  functions: functions that draw (like GpiLine), functions that set an
  6620.  attribute of the presentation space (like GpiMove and
  6621.  GpiSetCurrentPosition), and functions that query a presentation space
  6622.  attribute (like GpiQueryCurrentPosition). Most basic GPI functions fall into
  6623.  one of these three categories.
  6624.  
  6625.  Drawing Multiple Lines
  6626.  
  6627.  The current position stored in the presentation space allows you to draw a
  6628.  series of connected lines by making one call to GpiMove and multiple calls
  6629.  to GpiLine. However, for jobs of that type it is more efficient to use the
  6630.  GpiPolyLine function:
  6631.  
  6632.    GpiPolyLine (hps, lCount, aptl) ;
  6633.  
  6634.  The aptl parameter is an array of POINTL structures. The function draws
  6635.  lCount lines──the first from the current position to aptl[0], the second
  6636.  from aptl[0] to aptl[1], and so forth. The lCount parameter also indicates
  6637.  the number of points in the aptl array. When the function returns, the
  6638.  current point is set to the end of the last line it draws, the point
  6639.  aptl[lCount - 1].
  6640.  
  6641.  GpiPolyLine is functionally equivalent to the following:
  6642.  
  6643.    for (lIndex = 0 ; lIndex < lCount ; lIndex++)
  6644.         GpiLine (hps, aptl + lIndex) ;
  6645.  
  6646.  (Newcomers to C who are not yet entirely comfortable with the equivalence
  6647.  between array names and pointers might prefer the notation &aptl[lIndex]
  6648.  rather than aptl+lIndex.) However, any looping that GpiPolyLine performs
  6649.  occurs deep within a device driver. When drawing many connected lines,
  6650.  GpiPolyLine is much faster than multiple GpiLine calls.
  6651.  
  6652.  The STAR5 program in Figure 5-1 shows how to draw a five-pointed star using
  6653.  GpiMove and GpiPolyLine.
  6654.  
  6655.  Figure 5-1.  The STAR5 program.
  6656.  
  6657.    The STAR5 File
  6658.  
  6659.    #-----------------
  6660.    # STAR5 make file
  6661.    #-----------------
  6662.  
  6663.    star5.obj : star5.c
  6664.         cl -c -G2sw -W3 star5.c
  6665.  
  6666.    star5.exe : star5.obj star5.def
  6667.         link star5, /align:16, NUL, os2, star5
  6668.  
  6669.    The STAR5.C File
  6670.  
  6671.    /*---------------------------------
  6672.       STAR5.C -- Draws 5-Pointed Star
  6673.     ----------------------------------*/
  6674.  
  6675.    #include <os2.h>
  6676.  
  6677.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  6678.  
  6679.    int main (void)
  6680.         {
  6681.         static CHAR  szClientClass [] = "Star5" ;
  6682.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  6683.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  6684.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  6685.         HAB          hab ;
  6686.         HMQ          hmq ;
  6687.         HWND         hwndFrame, hwndClient ;
  6688.         QMSG         qmsg ;
  6689.  
  6690.         hab = WinInitialize (0) ;
  6691.         hmq = WinCreateMsgQueue (hab, 0) ;
  6692.  
  6693.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  6694.  
  6695.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  6696.                                         &flFrameFlags, szClientClass, NULL,
  6697.                                         0L, NULL, 0, &hwndClient) ;
  6698.  
  6699.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  6700.              WinDispatchMsg (hab, &qmsg) ;
  6701.  
  6702.         WinDestroyWindow (hwndFrame) ;
  6703.         WinDestroyMsgQueue (hmq) ;
  6704.         WinTerminate (hab) ;
  6705.         return 0 ;
  6706.         }
  6707.  
  6708.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  6709.         {
  6710.         static POINTL aptlStar[5] = {-59,-81, 0,100, 59,-81, -95,31, 95,31 } ;
  6711.         static SHORT  cxClient, cyClient ;
  6712.         HPS           hps ;
  6713.         POINTL        aptl[5] ;
  6714.         SHORT         sIndex ;
  6715.  
  6716.         switch (msg)
  6717.              {
  6718.              case WM_SIZE:
  6719.                   cxClient = SHORT1FROMMP (mp2) ;
  6720.                   cyClient = SHORT2FROMMP (mp2) ;
  6721.                   return 0 ;
  6722.  
  6723.              case WM_PAINT:
  6724.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  6725.                   GpiErase (hps) ;
  6726.  
  6727.                   for (sIndex = 0 ; sIndex < 5 ; sIndex++)
  6728.                        {
  6729.                        aptl[sIndex].x = cxClient / 2 + cxClient *
  6730.                                                     aptlStar[sIndex].x / 200 ;
  6731.                        aptl[sIndex].y = cyClient / 2 + cyClient *
  6732.                                                     aptlStar[sIndex].y / 200 ;
  6733.                        }
  6734.                   GpiMove (hps, aptl + 4) ;
  6735.                   GpiPolyLine (hps, 5L, aptl) ;
  6736.  
  6737.                   WinEndPaint (hps) ;
  6738.                   return 0 ;
  6739.              }
  6740.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  6741.         }
  6742.  
  6743.    The STAR5.DEF File
  6744.  
  6745.    ;----------------------------------
  6746.    ; STAR5.DEF module definition file
  6747.    ;----------------------------------
  6748.  
  6749.    NAME           STAR5     WINDOWAPI
  6750.  
  6751.    DESCRIPTION    'Draws 5-Pointed Star (C) Charles Petzold, 1988'
  6752.    PROTMODE
  6753.    HEAPSIZE       1024
  6754.    STACKSIZE      8192
  6755.    EXPORTS        ClientWndProc
  6756.  
  6757.  
  6758.  The aptlStar array contains the five POINTL structures that define the star.
  6759.  These are specified in "virtual" coordinates, that is, a coordinate system
  6760.  that I fabricated. The point (0,0) is the center of the star, and the star
  6761.  extends 100 units in all four directions. STAR5 must convert these points so
  6762.  that the star fills the client window, as shown in Figure 5-2.
  6763.  
  6764.  STAR5 converts the virtual coordinates to client window coordinates during
  6765.  the WM_PAINT message. The x fields of the POINTL structures are multiplied
  6766.  by cxClient and divided by 200. This adjusts for the window width. Then half
  6767.  of cxClient is added to move the center of the star to the center of the
  6768.  client window. The y fields are adjusted similarly, and the resultant points
  6769.  are stored in the aptl array.
  6770.  
  6771.  Notice how STAR5 calls GpiMove and GpiPolyLine to draw the star. First, it
  6772.  sets the current position to the last point in the array:
  6773.  
  6774.    GpiMove (hps, aptl + 4) ;
  6775.  
  6776.  (The expression aptl + 4 is equivalent to &aptl[4].) The GpiPolyLine
  6777.  function then draws five lines starting with a line to the first point in
  6778.  the array:
  6779.  
  6780.    GpiPolyLine (hps, 5L, aptl) ;
  6781.  
  6782.  The five lines that GpiPolyLine draws are as follows:
  6783.  
  6784.     Line              Begin Point       End Point
  6785.     1                 aptl[4]           aptl[0]
  6786.     2                 aptl[0]           aptl[1]
  6787.     3                 aptl[1]           aptl[2]
  6788.     4                 aptl[2]           aptl[3]
  6789.     5                 aptl[3]           aptl[4]
  6790.  
  6791.  It's necessary to initially set the current position to the last point in
  6792.  the array when the array defines a closed figure (like a star) and does not
  6793.  duplicate the first point. An alternative is to define an array of six
  6794.  POINTL structures, where the last point is the same as the first. In this
  6795.  case, you can draw the star by calling
  6796.  
  6797.    GpiMove (hps, aptl) ;
  6798.    GpiPolyLine (hps, 5L, aptl + 1) ;
  6799.  
  6800.  STAR5 is the first program in this book that looks good when minimized and
  6801.  displayed as an icon at the bottom of the screen. For that reason, I've
  6802.  removed the logic that tells the frame window to use the SPTR_APPICON for
  6803.  the minimized state. To STAR5, the minimized state is simply a very small
  6804.  client window. When the window is minimized, ClientWndProc receives a
  6805.  WM_SIZE message with the size of this tiny window and then receives a
  6806.  WM_PAINT message.
  6807.  
  6808.  Drawing Curves with GpiPolyLine
  6809.  
  6810.  The GpiPolyLine function is deceptive. The function seems to draw a series
  6811.  of straight lines, and it can certainly be used for that purpose. But
  6812.  GpiPolyLine has a more important role, which is to draw curves. To do this,
  6813.  simply call GpiPolyLine with a POINTL array that defines many tiny lines.
  6814.  
  6815.  Don't hesitate to call GpiPolyLine with an array of hundreds──or even
  6816.  thousands──of points. That's the purpose of the function. Because
  6817.  GpiPolyLine is interpreted by the device driver, it is very fast. The
  6818.  maximum number of points currently allowed for a GpiPolyLine call is 8000.
  6819.  This limit is based on the size of a POINTL structure (8 bytes) and the
  6820.  maximum size of the memory segment under the 80286 microprocessor (64 KB).
  6821.  
  6822.  Any curve that you can define mathematically you can draw as a series of
  6823.  straight lines using GpiPolyLine. For example, suppose you want to draw one
  6824.  cycle of a sine curve in your client window. You can define an array of 100
  6825.  POINTL structures and set the points to define the sine curve:
  6826.  
  6827.    #include <math.h>            // for sin declaration
  6828.         ∙
  6829.         ∙
  6830.         ∙
  6831.    POINTL aptl[100] ;
  6832.    SHORT  sIndex ;
  6833.         ∙
  6834.         ∙
  6835.         ∙
  6836.    for (sIndex = 0 ; sIndex < 100 ; sIndex++)
  6837.         {
  6838.         aptl[sIndex].x = (LONG) sIndex * cxClient / 100 ;
  6839.         aptl[sIndex].y = (LONG) (cyClient / 2 * (1 + sin (sIndex * 6.28 / 100)
  6840.         }
  6841.  
  6842.  The x fields of the POINTL structures range from 0 to cxClient. The y field
  6843.  is the value of the sin function over one period, scaled to the height of
  6844.  the client window.
  6845.  
  6846.  To draw the sine curve, begin by setting the current position to the first
  6847.  point as follows:
  6848.  
  6849.    GpiMove (hps, aptl) ;
  6850.  
  6851.  You then use the GpiPolyLine function to draw 99 lines beginning at the
  6852.  second point:
  6853.  
  6854.    GpiPolyLine (hps, 99L, aptl + 1) ;
  6855.  
  6856.  Curves and Parametric Equations
  6857.  
  6858.  The sine curve is relatively easy because the y coordinate is a simple
  6859.  function of the x coordinate. In general, however, this is not the case.
  6860.  There might be multiple y values for each value of x. A more generalized
  6861.  approach to drawing curves uses "parametric" equations.
  6862.  
  6863.  In parametric equations, both the x and y coordinates of every point are
  6864.  calculated from functions based on a third variable, often called t.
  6865.  Intuitively, you can think of t as time or as some other abstract index
  6866.  necessary to define the entire curve. When you draw a curve using GPI
  6867.  functions, the values of t will range from 0 to the number of points that
  6868.  are in the POINTL array.
  6869.  
  6870.  For example, suppose you want to draw an ellipse that fills your client
  6871.  window. You can start with parametric equations that define a unit circle:
  6872.  
  6873.    x(t) = cos(t)
  6874.    y(t) = sin(t)
  6875.  
  6876.  For t ranging from 0 degrees to 2 * π radians, these equations define a
  6877.  circle around the point (0,0) with a radius of 1. The ellipse is defined
  6878.  similarly:
  6879.  
  6880.    x(t) = RX cos(t)
  6881.    y(t) = RY sin(t)
  6882.  
  6883.  The two axes of the ellipse are parallel to the horizontal and vertical
  6884.  axes. The horizontal ellipse axis is 2 * RX in length; the vertical ellipse
  6885.  axis is 2 * RY. The ellipse is still centered around (0,0). To center it
  6886.  around the point (CX,CY), the formulas are
  6887.  
  6888.    x(t) = CX + RX cos(t)
  6889.    y(t) = CY + RY sin(t)
  6890.  
  6891.  Here's the code to draw an ellipse centered in the client window:
  6892.  
  6893.    #include <math.h>            // for sin and cos declaration
  6894.    ∙
  6895.    ∙
  6896.    ∙
  6897.  
  6898.    double dAngle ;
  6899.    POINTL aptl[100] ;
  6900.    SHORT  sIndex ;
  6901.    ∙
  6902.    ∙
  6903.    ∙
  6904.  
  6905.    for (sIndex = 0 ; sIndex <= 100 ; sIndex ++)
  6906.         {
  6907.         dAngle = sIndex * 6.28 / 100 ;
  6908.  
  6909.  
  6910.  
  6911.  In this case, both RX and CX are equal to cxClient/2, and RY and CY are
  6912.  equal to cyClient/2.
  6913.  
  6914.  The SPIRAL program shown in Figure 5-3 uses a variation of these formulas
  6915.  to draw a spiral in its client window.
  6916.  
  6917.  Figure 5-3.  The SPIRAL program.
  6918.  
  6919.    The SPIRAL File
  6920.    #------------------
  6921.    # SPIRAL make file
  6922.    #------------------
  6923.  
  6924.    spiral.obj : spiral.c
  6925.         cl -c -G2sw -W3 spiral.c
  6926.  
  6927.    spiral.exe : spiral.obj spiral.def
  6928.         link spiral, /align:16, NUL, os2, spiral
  6929.  
  6930.    The SPIRAL.C File
  6931.  
  6932.    /*--------------------------------
  6933.       SPIRAL.C -- GPI Spiral Drawing
  6934.     ---------------------------------*/
  6935.  
  6936.    #include <os2.h>
  6937.    #include <math.h>
  6938.  
  6939.    #define NUMPOINTS 1000
  6940.    #define NUMREV    20
  6941.    #define PI        3.14159
  6942.  
  6943.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  6944.  
  6945.    int main (void)
  6946.         {
  6947.         static CHAR  szClientClass [] = "Spiral" ;
  6948.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  6949.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  6950.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  6951.         HAB          hab ;
  6952.         HMQ          hmq ;
  6953.         HWND         hwndFrame, hwndClient ;
  6954.         QMSG         qmsg ;
  6955.  
  6956.         hab = WinInitialize (0) ;
  6957.         hmq = WinCreateMsgQueue (hab, 0) ;
  6958.  
  6959.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  6960.  
  6961.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  6962.                                         &flFrameFlags, szClientClass, NULL,
  6963.                                         0L, NULL, 0, &hwndClient) ;
  6964.  
  6965.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  6966.              WinDispatchMsg (hab, &qmsg) ;
  6967.  
  6968.         WinDestroyWindow (hwndFrame) ;
  6969.         WinDestroyMsgQueue (hmq) ;
  6970.         WinTerminate (hab) ;
  6971.         return 0 ;
  6972.         }
  6973.  
  6974.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  6975.         {
  6976.         static SHORT cxClient, cyClient ;
  6977.         double       dAngle, dScale ;
  6978.         HPS          hps ;
  6979.         PPOINTL      pptl ;
  6980.         SEL          sel ;
  6981.         SHORT        sIndex ;
  6982.  
  6983.         switch (msg)
  6984.              {
  6985.              case WM_SIZE:
  6986.                   cxClient = SHORT1FROMMP (mp2) ;
  6987.                   cyClient = SHORT2FROMMP (mp2) ;
  6988.                   return 0 ;
  6989.  
  6990.              case WM_PAINT:
  6991.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  6992.                   GpiErase (hps) ;
  6993.  
  6994.                   if (!DosAllocSeg (NUMPOINTS * sizeof (POINTL), &sel, 0))
  6995.                        {
  6996.                        pptl = MAKEP (sel, 0) ;
  6997.  
  6998.                        for (sIndex = 0 ; sIndex < NUMPOINTS ; sIndex ++)
  6999.                            {
  7000.                            dAngle = sIndex * 2 * PI / (NUMPOINTS / NUMREV) ;
  7001.                            dScale = 1 - (double) sIndex / NUMPOINTS ;
  7002.  
  7003.                            pptl[sIndex].x = (LONG) (cxClient / 2 *
  7004.                                                     (1 + dScale * cos (dAngle)
  7005.  
  7006.                            pptl[sIndex].y = (LONG) (cyClient / 2 *
  7007.                                                     (1 + dScale * sin (dAngle)
  7008.                            }
  7009.                        GpiMove (hps, pptl) ;
  7010.                        GpiPolyLine (hps, NUMPOINTS - 1L, pptl + 1) ;
  7011.  
  7012.                        DosFreeSeg (sel) ;
  7013.                        }
  7014.                   WinEndPaint (hps) ;
  7015.                   return 0 ;
  7016.              }
  7017.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  7018.         }
  7019.  
  7020.    The SPIRAL.DEF File
  7021.  
  7022.    ;-----------------------------------
  7023.    ; SPIRAL.DEF module definition file
  7024.    ;-----------------------------------
  7025.  
  7026.    NAME           SPIRAL    WINDOWAPI
  7027.  
  7028.    DESCRIPTION    'GPI Spiral Using a Polyline (C) Charles Petzold, 1988'
  7029.    PROTMODE
  7030.    HEAPSIZE       1024
  7031.    STACKSIZE      8192
  7032.    EXPORTS        ClientWndProc
  7033.  
  7034.  
  7035.  In effect, SPIRAL draws 20 ellipses but uniformly decreases the length of
  7036.  the axes to create a spiral as shown in Figure 5-4.
  7037.  
  7038.  SPIRAL uses 1000 points to describe this figure. The program allocates a
  7039.  block of memory for this array by calling the OS/2 DosAllocSeg function.
  7040.  This function returns a selector (segment address) to the memory block,
  7041.  which is stored in the variable sel. The MAKEP macro makes a far (32-bit)
  7042.  pointer from sel and stores it in pptl. Note that pptl is not defined as a
  7043.  POINTL variable, but as a PPOINTL. PPOINTL is defined in OS2DEF.H as a far
  7044.  pointer to a POINTL structure:
  7045.  
  7046.    typedef POINTL FAR *PPOINTL ;
  7047.  
  7048.  The segment is freed after the drawing is finished.
  7049.  
  7050.  You can also use the C malloc and free functions for allocating memory to
  7051.  store arrays of POINTL structures, in which case you would want to define
  7052.  the pptl pointer like this:
  7053.  
  7054.    POINTL *pptl ;
  7055.  
  7056.  Whether pptl is a far pointer or near pointer now depends on what memory
  7057.  model you specify when compiling the program. This will be compatible with
  7058.  the pointer returned from malloc and passed to free.
  7059.  
  7060.  The Line Type
  7061.  
  7062.  Up until now all the lines we have drawn have been solid lines. You can also
  7063.  draw lines composed of various dots and dashes. This is called the "line
  7064.  type attribute" and you set it with the GpiSetLineType function:
  7065.  
  7066.    GpiSetLineType (hps, lLineType) ;
  7067.  
  7068.  The lLineType parameter is one of the following identifiers defined in
  7069.  PMGPI.H:
  7070.  
  7071.     LINETYPE_DEFAULT           LINETYPE_LONGDASH
  7072.     LINETYPE_DOT               LINETYPE_DASHDOUBLEDOT
  7073.     LINETYPE_SHORTDASH         LINETYPE_SOLID
  7074.     LINETYPE_DASHDOT           LINETYPE_INVISIBLE
  7075.     LINETYPE_DOUBLEDOT         LINETYPE_ALTERNATE
  7076.  
  7077.  These identifiers are fairly self-explanatory. The LINETYPE_DEFAULT
  7078.  identifier (defined as 0L) has the same effect as LINETYPE_SOLID. The
  7079.  LINETYPE_ALTERNATE style draws every other pixel, giving the appearance of a
  7080.  gray line.
  7081.  
  7082.  The line type is an attribute of the presentation space. When you set the
  7083.  line type, it affects all subsequent lines you draw until you change the
  7084.  line type again or release the presentation space.
  7085.  
  7086.  You can determine the current line type by calling
  7087.  
  7088.    lLineType = GpiQueryLineType (hps) ;
  7089.  
  7090.  However, if you call GpiQueryLineType for a new presentation space without
  7091.  first calling GpiSetLineType, the function returns an identifier of
  7092.  LINETYPE_DEFAULT rather than LINETYPE_SOLID.
  7093.  
  7094.  The LINETYPE program (Figure 5-5) displays lines drawn with each of these
  7095.  line types so that you can see what they look like.
  7096.  
  7097.  Figure 5-5.  The LINETYPE program.
  7098.  
  7099.    The LINETYPE File
  7100.  
  7101.    #--------------------
  7102.    # LINETYPE make file
  7103.    #--------------------
  7104.  
  7105.    linetype.obj : linetype.c
  7106.         cl -c -G2sw -W3 linetype.c
  7107.  
  7108.    linetype.exe : linetype.obj linetype.def
  7109.         link linetype, /align:16, NUL, os2, linetype
  7110.  
  7111.    The LINETYPE.C File
  7112.  
  7113.    /*------------------------------
  7114.       LINETYPE.C -- GPI Line Types
  7115.      ------------------------------*/
  7116.  
  7117.    #define INCL_WIN
  7118.    #define INCL_GPI
  7119.    #include <os2.h>
  7120.    #include <string.h>
  7121.  
  7122.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  7123.  
  7124.    int main (void)
  7125.         {
  7126.         static CHAR  szClientClass [] = "LineType" ;
  7127.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  7128.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  7129.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  7130.         HAB          hab ;
  7131.         HMQ          hmq ;
  7132.         HWND         hwndFrame, hwndClient ;
  7133.         QMSG         qmsg ;
  7134.  
  7135.         hab = WinInitialize (0) ;
  7136.         hmq = WinCreateMsgQueue (hab, 0) ;
  7137.  
  7138.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  7139.  
  7140.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  7141.                                         &flFrameFlags, szClientClass, NULL,
  7142.                                         0L, NULL, 0, &hwndClient) ;
  7143.  
  7144.         WinSendMsg (hwndFrame, WM_SETICON,
  7145.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  7146.                     NULL) ;
  7147.  
  7148.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  7149.              WinDispatchMsg (hab, &qmsg) ;
  7150.  
  7151.         WinDestroyWindow (hwndFrame) ;
  7152.         WinDestroyMsgQueue (hmq) ;
  7153.         WinTerminate (hab) ;
  7154.         return 0 ;
  7155.         }
  7156.  
  7157.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  7158.         {
  7159.         static struct {
  7160.                       LONG lLineType ;
  7161.                       CHAR *szLineType ;
  7162.                       }
  7163.                       show [] =
  7164.                       {
  7165.                       LINETYPE_DEFAULT       , "LINETYPE_DEFAULT"       ,
  7166.                       LINETYPE_DOT           , "LINETYPE_DOT"           ,
  7167.                       LINETYPE_SHORTDASH     , "LINETYPE_SHORTDASH"     ,
  7168.                       LINETYPE_DASHDOT       , "LINETYPE_DASHDOT"       ,
  7169.                       LINETYPE_DOUBLEDOT     , "LINETYPE_DOUBLEDOT"     ,
  7170.                       LINETYPE_LONGDASH      , "LINETYPE_LONGDASH"      ,
  7171.                       LINETYPE_DASHDOUBLEDOT , "LINETYPE_DASHDOUBLEDOT" ,
  7172.                       LINETYPE_SOLID         , "LINETYPE_SOLID"         ,
  7173.                       LINETYPE_INVISIBLE     , "LINETYPE_INVISIBLE"     ,
  7174.                       LINETYPE_ALTERNATE     , "LINETYPE_ALTERNATE"
  7175.                       } ;
  7176.         static SHORT  cxClient, cyClient, cxCaps, cyChar, cyDesc,
  7177.                       sNumTypes = sizeof show / sizeof show[0] ;
  7178.         FONTMETRICS   fm ;
  7179.         HPS           hps ;
  7180.         POINTL        ptl ;
  7181.         SHORT         sIndex ;
  7182.  
  7183.         switch (msg)
  7184.              {
  7185.              case WM_CREATE:
  7186.                   hps = WinGetPS (hwnd) ;
  7187.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  7188.                   cxCaps = (SHORT) fm.lEmInc ;
  7189.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  7190.                   cyDesc = (SHORT) fm.lMaxDescender ;
  7191.                   WinReleasePS (hps) ;
  7192.                   return 0 ;
  7193.  
  7194.              case WM_SIZE:
  7195.                   cxClient = SHORT1FROMMP (mp2) ;
  7196.                   cyClient = SHORT2FROMMP (mp2) ;
  7197.                   return 0 ;
  7198.  
  7199.              case WM_PAINT:
  7200.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  7201.                   GpiErase (hps) ;
  7202.  
  7203.                   for (sIndex = 0 ; sIndex < sNumTypes ; sIndex ++)
  7204.                        {
  7205.                        GpiSetLineType (hps, show [sIndex].lLineType) ;
  7206.  
  7207.                        ptl.x = cxCaps ;
  7208.                        ptl.y = cyClient - 2 * (sIndex + 1) * cyChar + cyDesc ;
  7209.  
  7210.                        GpiCharStringAt (hps, &ptl,
  7211.                                         (LONG) strlen (show [sIndex].szLineTyp
  7212.                                         show [sIndex].szLineType) ;
  7213.  
  7214.                        if (cxClient > 25 * cxCaps)
  7215.                             {
  7216.                             ptl.x = 24 * cxCaps ;
  7217.                             ptl.y += cyChar / 2 - cyDesc ;
  7218.                             GpiMove (hps, &ptl) ;
  7219.  
  7220.                             ptl.x = cxClient - cxCaps ;
  7221.                             GpiLine (hps, &ptl) ;
  7222.                             }
  7223.                        }
  7224.                   WinEndPaint (hps) ;
  7225.                   return 0 ;
  7226.              }
  7227.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  7228.         }
  7229.  
  7230.    The LINETYPE.DEF File
  7231.  
  7232.    ;-------------------------------------
  7233.    ; LINETYPE.DEF module definition file
  7234.    ;-------------------------------------
  7235.  
  7236.    NAME           LINETYPE  WINDOWAPI
  7237.  
  7238.    DESCRIPTION    'GPI Line Types (C) Charles Petzold, 1988'
  7239.    PROTMODE
  7240.    HEAPSIZE       1024
  7241.    STACKSIZE      8192
  7242.    EXPORTS        ClientWndProc
  7243.  
  7244.  
  7245.  The results are shown in Figure 5-6.
  7246.  
  7247.  Each line type is a short sequence of dots or dashes that is repeated over
  7248.  the length of the line. You can use these line types when drawing multiple
  7249.  lines (even very short ones) with GpiPolyLine. When drawing each line, the
  7250.  device driver keeps track of which part of the short sequence it drew in the
  7251.  last line. The next line picks up where the last line ended. You can also
  7252.  use the line types with successive GpiLine calls. However, the device driver
  7253.  resets its position to the beginning of the sequence when you call GpiMove,
  7254.  GpiSetCurrentPosition, or GpiSetLineType.
  7255.  
  7256.  Boxes and a Simple Ellipse
  7257.  
  7258.  Probably the most common closed figure is a rectangle. You can draw a
  7259.  rectangle with one GpiMove and four GpiLine calls, or you can use the
  7260.  function that GPI provides:
  7261.  
  7262.    GpiBox (hps, lOption, &ptl, 0L, 0L) ;
  7263.  
  7264.  The GpiBox function draws a rectangle with sides parallel to the x and y
  7265.  axes. The position and size of the rectangle are defined by any two opposite
  7266.  corners of the rectangle. GpiBox uses the current position for one corner
  7267.  and the POINTL structure passed to the function for the opposite corner.
  7268.  GpiBox does not change the current position.
  7269.  
  7270.  The lOption parameter can be one of the following identifiers defined in
  7271.  PMGPI.H:
  7272.  
  7273.    DRO_FILL
  7274.    DRO_OUTLINE
  7275.    DRO_OUTLINEFILL
  7276.  
  7277.  DRO_FILL causes the rectangle to be filled. The DRO_OUTLINE option directs
  7278.  GPI to draw only the outline of the rectangle. DRO_OUTLINEFILL draws the
  7279.  outline and fills the rectangle. GPI uses the current line type for drawing
  7280.  the outline. How GPI fills the interior of the rectangle is discussed in the
  7281.  following section on patterned areas.
  7282.  
  7283.  Suppose cxClient and cyClient are the width and height of your client
  7284.  window. You want to draw an unfilled rectangle that is one half that width
  7285.  and height and centered in the client window. Here's the code:
  7286.  
  7287.    ptl.x = xClient / 4 ;
  7288.    ptl.y = yClient / 4 ;
  7289.    GpiMove (hps, &ptl) ;
  7290.  
  7291.    ptl.x *= 3 ;
  7292.    ptl.y *= 3 ;
  7293.    GpiBox (hps, DRO_OUTLINE, &ptl, 0L, 0L) ;
  7294.  
  7295.  You can set the last two parameters of GpiBox to values greater than 0 to
  7296.  draw a rectangle with rounded corners. The general syntax of GpiBox is
  7297.  
  7298.    GpiBox (hps, lOption, &ptl, cxEllipseAxis, cyEllipseAxis) ;
  7299.  
  7300.  The last two parameters define the width and height of an ellipse. (These
  7301.  dimensions must be less than or equal to the width and height of the
  7302.  rectangle being drawn.) You can visualize GPI cutting this ellipse into four
  7303.  quadrants and using each quadrant of the ellipse as a corner of the box.
  7304.  
  7305.  If cxEllipseAxis and cyEllipseAxis are set equal to the width and height of
  7306.  the rectangle being drawn, then GpiBox draws an ellipse. Here's a simple
  7307.  ellipse function that calculates the last two parameters of GpiBox:
  7308.  
  7309.    #include <stdlib.h>          // For labs declaration
  7310.         ∙
  7311.         ∙
  7312.         ∙
  7313.  
  7314.    LONG Ellipse (HPS hps, LONG lOption, POINTL *pptl)
  7315.         {
  7316.         POINTL ptlCurrent ;
  7317.  
  7318.         GpiQueryCurrentPosition (hps, &ptlCurrent) ;
  7319.  
  7320.         return GpiBox (hps, lOption, pptl, labs (pptl->x - ptlCurrent.x),
  7321.                                            labs (pptl->y - ptlCurrent.y)) ;
  7322.         }
  7323.  
  7324.  Like GpiBox, this Ellipse function draws a figure with axes parallel to the
  7325.  sides of the window. GPI provides even more versatile ellipse drawing
  7326.  facilities with the GpiSetArcParams, GpiFullArc, GpiPointArc, and
  7327.  GpiPartialArc functions. Other GPI functions that draw curves are
  7328.  GpiPolySpline, GpiPolyFillet, and GpiPolyFilletSharp.
  7329.  
  7330.  
  7331.  Pixels and Device Independence
  7332.  
  7333.  Until now, we've been working in a coordinate system based on units of
  7334.  pixels. To some people familiar with other graphics programming languages,
  7335.  the idea of working in units of pixels may seem a contradiction to the goal
  7336.  of writing device-independent programs. After all, what can be more
  7337.  device-dependent than pixels?
  7338.  
  7339.  Pixels certainly have problems. The first is resolution. Almost every
  7340.  graphics output device has a different pixel resolution. A 100-pixel-high
  7341.  image on an IBM Color Graphics Adapter will encompass half the height of the
  7342.  screen. On a 300-dots-per-inch laser printer, it will be 1/3 inch high.
  7343.  Second, many video display adapters and dot-matrix printers use different
  7344.  horizontal and vertical resolutions.
  7345.  
  7346.  Let's examine some ways to deal with these problems.
  7347.  
  7348.  Simple Techniques
  7349.  
  7350.  If you draw in units of pixels, you can use pixels in a device-independent
  7351.  manner. One simple technique (used in the SYSVALS programs in Chapter 4 and
  7352.  the LINETYPE program earlier in this chapter) involves basing all
  7353.  coordinates and dimensions on the size of the standard system font
  7354.  characters.
  7355.  
  7356.  This technique is particularly useful when a program combines text with some
  7357.  rudimentary graphics. For example, suppose you want to write a simple
  7358.  database program using an index card metaphor. Each record is displayed in a
  7359.  simulated 3 * 5-inch index card on the screen. How large are the index cards
  7360.  in pixels? Think of a typewriter. A typewriter with a pica typeface types 10
  7361.  characters per inch horizontally with 6 lines to the inch vertically. Thus a
  7362.  3 * 5 card can fit 18 rows of 50 characters each. If cxChar and cyChar are
  7363.  the average width and height of a system font character, then each card is
  7364.  (50 * cxChar) pixels wide and (18 * cyChar) pixels high.
  7365.  
  7366.  Sometimes you need to display only graphics in your window and you want the
  7367.  size of the objects to be based on the size of the window. In this case, you
  7368.  can use the technique shown earlier in the STAR5 program. The five-pointed
  7369.  star in that program is defined in a virtual coordinate system centered
  7370.  around the point (0,0) with a width of 200 units and a height of 200 units.
  7371.  Before drawing the object, the program scales these units to the size of the
  7372.  client window and translates the points so that (0,0) corresponds to the
  7373.  center of the window.
  7374.  
  7375.  Of course, for some applications these approaches are not satisfactory at
  7376.  all. For example, how do you draw a square with sides of equal length? If
  7377.  the output device has different horizontal and vertical resolutions, then
  7378.  the horizontal and vertical dimensions of the object must be scaled
  7379.  differently.
  7380.  
  7381.  The Device Context and Its Capabilities
  7382.  
  7383.  You'll recall from Chapter 4 that a "device context" refers to a graphics
  7384.  output device (such as a video display or a printer) and its device driver.
  7385.  A presentation space is associated with a particular device context. A
  7386.  cached micro-PS is always associated with the device context for the video
  7387.  display.
  7388.  
  7389.  A program can obtain lots of interesting information about an output device
  7390.  ──including everything it needs to accurately scale graphics objects──by
  7391.  calling the DevQueryCaps ("query capabilities") function. To use
  7392.  DevQueryCaps for the video display, you first need a handle to the video
  7393.  display device context. You can obtain this easily during WM_CREATE
  7394.  processing by calling WinOpenWindowDC as shown on the next page.
  7395.  
  7396.    static HDC hdc ;
  7397.         ∙
  7398.         ∙
  7399.         ∙
  7400.  
  7401.    hdc = WinOpenWindowDC (hwnd) ;
  7402.  
  7403.  Or, you can obtain a handle to the device context associated with a
  7404.  presentation space by calling
  7405.  
  7406.    hdc = GpiQueryDevice (hps) ;
  7407.  
  7408.  The PMDEV.H header file defines 39 identifiers, each beginning with the word
  7409.  CAPS, that you use with DevQueryCaps. Each identifer obtains a particular
  7410.  item that describes the device. Although you can obtain information about
  7411.  multiple items, it's easier to use DevQueryCaps for only one item at a time:
  7412.  
  7413.    LONG lCapsValue ;
  7414.         ∙
  7415.         ∙
  7416.         ∙
  7417.  
  7418.    DevQueryCaps (hdc, CAPS... , 1L, &lCapsValue) ;
  7419.  
  7420.  The DEVCAPS program shown in Figure 5-7 obtains all the information
  7421.  available from DevQueryCaps and displays it in a simple two-column format.
  7422.  
  7423.  Figure 5-7.  The DEVCAPS program.
  7424.  
  7425.    The DEVCAPS File
  7426.  
  7427.    #-------------------
  7428.    # DEVCAPS make file
  7429.    #-------------------
  7430.  
  7431.    devcaps.obj : devcaps.c devcaps.h
  7432.         cl -c -G2sw -W3 devcaps.c
  7433.  
  7434.    devcaps.exe : devcaps.obj devcaps.def
  7435.         link devcaps, /align:16, NUL, os2, devcaps
  7436.  
  7437.    The DEVCAPS.C File
  7438.  
  7439.    /*--------------------------------------------------
  7440.       DEVCAPS.C -- Device Capabilities Display Program
  7441.      --------------------------------------------------*/
  7442.  
  7443.    #define INCL_WIN
  7444.    #define INCL_GPI
  7445.    #include <os2.h>
  7446.    #include <stdlib.h>
  7447.    #include <string.h>
  7448.    #include "devcaps.h"
  7449.  
  7450.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  7451.  
  7452.    int main (void)
  7453.         {
  7454.         static CHAR  szClientClass [] = "DevCaps" ;
  7455.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  7456.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  7457.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  7458.         HAB          hab ;
  7459.         HMQ          hmq ;
  7460.         HWND         hwndFrame, hwndClient ;
  7461.         QMSG         qmsg ;
  7462.  
  7463.         hab = WinInitialize (0) ;
  7464.         hmq = WinCreateMsgQueue (hab, 0) ;
  7465.  
  7466.         WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;
  7467.  
  7468.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  7469.                                         &flFrameFlags, szClientClass, NULL,
  7470.                                         0L, NULL, 0, &hwndClient) ;
  7471.  
  7472.         WinSendMsg (hwndFrame, WM_SETICON,
  7473.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  7474.                     NULL) ;
  7475.  
  7476.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  7477.              WinDispatchMsg (hab, &qmsg) ;
  7478.  
  7479.         WinDestroyWindow (hwndFrame) ;
  7480.         WinDestroyMsgQueue (hmq) ;
  7481.         WinTerminate (hab) ;
  7482.         return 0 ;
  7483.         }
  7484.  
  7485.    LONG RtJustCharStringAt (HPS hps, POINTL *pptl, LONG lLength, CHAR *pchText
  7486.         {
  7487.         POINTL aptlTextBox[TXTBOX_COUNT] ;
  7488.  
  7489.         GpiQueryTextBox (hps, lLength, pchText, TXTBOX_COUNT, aptlTextBox) ;
  7490.  
  7491.         pptl->x -= aptlTextBox[TXTBOX_CONCAT].x ;
  7492.  
  7493.         return GpiCharStringAt (hps, pptl, lLength, pchText) ;
  7494.         }
  7495.  
  7496.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  7497.         {
  7498.         static HDC   hdc ;
  7499.         static SHORT cxClient, cyClient, cxCaps, cyChar, cyDesc ;
  7500.         CHAR         szBuffer [12] ;
  7501.         FONTMETRICS  fm ;
  7502.         LONG         lValue ;
  7503.         POINTL       ptl ;
  7504.         HPS          hps ;
  7505.         SHORT        sLine ;
  7506.  
  7507.         switch (msg)
  7508.              {
  7509.              case WM_CREATE:
  7510.                   hps = WinGetPS (hwnd) ;
  7511.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  7512.                   cxCaps = (SHORT) fm.lEmInc ;
  7513.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  7514.                   cyDesc = (SHORT) fm.lMaxDescender ;
  7515.                   WinReleasePS (hps) ;
  7516.  
  7517.                   hdc = WinOpenWindowDC (hwnd) ;
  7518.                   return 0 ;
  7519.  
  7520.              case WM_SIZE:
  7521.                   cxClient = SHORT1FROMMP (mp2) ;
  7522.                   cyClient = SHORT2FROMMP (mp2) ;
  7523.                   return 0 ;
  7524.  
  7525.              case WM_PAINT:
  7526.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  7527.                   GpiErase (hps) ;
  7528.  
  7529.                   for (sLine = 0 ; sLine < NUMLINES ; sLine++)
  7530.                        {
  7531.                        ptl.x = cxCaps ;
  7532.                        ptl.y = cyClient - cyChar * (sLine + 2) + cyDesc ;
  7533.  
  7534.                        if (sLine >= (NUMLINES + 1) / 2)
  7535.                             {
  7536.                             ptl.x += cxCaps * 35 ;
  7537.                             ptl.y += cyChar * (NUMLINES + 1) / 2 ;
  7538.                             }
  7539.  
  7540.                        DevQueryCaps (hdc, devcaps[sLine].lIndex, 1L, &lValue)
  7541.  
  7542.                        GpiCharStringAt (hps, &ptl,
  7543.                                  (LONG) strlen (devcaps[sLine].szIdentifier),
  7544.                                  devcaps[sLine].szIdentifier) ;
  7545.  
  7546.                        ptl.x += 33 * cxCaps ;
  7547.                        RtJustCharStringAt (hps, &ptl,
  7548.                                  (LONG) strlen (ltoa (lValue, szBuffer, 10)),
  7549.                                  szBuffer) ;
  7550.                        }
  7551.                   WinEndPaint (hps) ;
  7552.                   return 0 ;
  7553.              }
  7554.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  7555.         }
  7556.  
  7557.    The DEVCAPS.H File
  7558.  
  7559.    /*-----------------------
  7560.       DEVCAPS.H header file
  7561.      -----------------------*/
  7562.  
  7563.    #define NUMLINES (sizeof devcaps / sizeof devcaps [0])
  7564.  
  7565.    struct
  7566.         {
  7567.         LONG lIndex ;
  7568.  
  7569.         CHAR *szIdentifier ;
  7570.         }
  7571.         devcaps [] =
  7572.         {
  7573.         CAPSFAMILY               , "CAPSFAMILY"              ,
  7574.         CAPSIOCAPS               , "CAPSIOCAPS"              ,
  7575.         CAPSTECHNOLOGY           , "CAPSTECHNOLOGY"          ,
  7576.         CAPSDRIVERVERSION        , "CAPSDRIVERVERSION"       ,
  7577.         CAPSHEIGHT               , "CAPSHEIGHT"              ,
  7578.         CAPSWIDTH                , "CAPSWIDTH"               ,
  7579.         CAPSHEIGHTINCHARS        , "CAPSHEIGHTINCHARS"       ,
  7580.         CAPSWIDTHINCHARS         , "CAPSWIDTHINCHARS"        ,
  7581.         CAPSVERTICALRESOLUTION   , "CAPSVERTICALRESOLUTION"  ,
  7582.         CAPSHORIZONTALRESOLUTION , "CAPSHORIZONTALRESOLUTION",
  7583.         CAPSCHARHEIGHT           , "CAPSCHARHEIGHT"          ,
  7584.         CAPSCHARWIDTH            , "CAPSCHARWIDTH"           ,
  7585.         CAPSSMALLCHARHEIGHT      , "CAPSSMALLCHARHEIGHT"     ,
  7586.         CAPSSMALLCHARWIDTH       , "CAPSSMALLCHARWIDTH"      ,
  7587.         CAPSCOLORS               , "CAPSCOLORS"              ,
  7588.         CAPSCOLORPLANES          , "CAPSCOLORPLANES"         ,
  7589.         CAPSCOLORBITCOUNT        , "CAPSCOLORBITCOUNT"       ,
  7590.         CAPSCOLORTABLESUPPORT    , "CAPSCOLORTABLESUPPORT"   ,
  7591.         CAPSMOUSEBUTTONS         , "CAPSMOUSEBUTTONS"        ,
  7592.         CAPSFOREGROUNDMIXSUPPORT , "CAPSFOREGROUNDMIXSUPPORT",
  7593.         CAPSBACKGROUNDMIXSUPPORT , "CAPSBACKGROUNDMIXSUPPORT",
  7594.         CAPSVIOLOADABLEFONTS     , "CAPSVIOLOADABLEFONTS"    ,
  7595.         CAPSWINDOWBYTEALIGNMENT  , "CAPSWINDOWBYTEALIGNMENT" ,
  7596.         CAPSBITMAPFORMATS        , "CAPSBITMAPFORMATS"       ,
  7597.         CAPSRASTERCAPS           , "CAPSRASTERCAPS"          ,
  7598.         CAPSMARKERHEIGHT         , "CAPSMARKERHEIGHT"        ,
  7599.         CAPSMARKERWIDTH          , "CAPSMARKERWIDTH"         ,
  7600.         CAPSDEVICEFONTS          , "CAPSDEVICEFONTS"         ,
  7601.         CAPSGRAPHICSSUBSET       , "CAPSGRAPHICSSUBSET"      ,
  7602.         CAPSGRAPHICSVERSION      , "CAPSGRAPHICSVERSION"     ,
  7603.         CAPSGRAPHICSVECTORSUBSET , "CAPSGRAPHICSVECTORSUBSET",
  7604.         CAPSDEVICEWINDOWING      , "CAPSDEVICEWINDOWING"     ,
  7605.         CAPSADDITIONALGRAPHICS   , "CAPSADDITIONALGRAPHICS"  ,
  7606.         CAPSPHYSCOLORS           , "CAPSPHYSCOLORS"          ,
  7607.         CAPSCOLORINDEX           , "CAPSCOLORINDEX"          ,
  7608.         CAPSGRAPHICSCHARWIDTH    , "CAPSGRAPHICSCHARWIDTH"   ,
  7609.         CAPSGRAPHICSCHARHEIGHT   , "CAPSGRAPHICSCHARHEIGHT"  ,
  7610.         CAPSHORIZONTALFONTRES    , "CAPSHORIZONTALFONTRES"   ,
  7611.         CAPSVERTICALFONTRES      , "CAPSVERTICALFONTRES"
  7612.         } ;
  7613.  
  7614.    The DEVCAPS.DEF File
  7615.  
  7616.    ;------------------------------------
  7617.    ; DEVCAPS.DEF module definition file
  7618.    ;------------------------------------
  7619.  
  7620.    NAME           DEVCAPS   WINDOWAPI
  7621.  
  7622.    DESCRIPTION    'Device Capabilities (C) Charles Petzold, 1988'
  7623.    PROTMODE
  7624.    HEAPSIZE       1024
  7625.    STACKSIZE      8192
  7626.    EXPORTS        ClientWndProc
  7627.  
  7628.  
  7629.  When the Presentation Manager is running on an IBM Enhanced Graphics
  7630.  Adapter, DEVCAPS returns the information shown in Figure 5-8.
  7631.  
  7632.  Some information is encoded in bits in the return values. You'll need the
  7633.  Presentation Manager documentation and the PMDEV.H header file in order to
  7634.  decode it. For now, we'll look at four items: CAPS_HEIGHT and CAPS_WIDTH
  7635.  give the pixel dimensions of the output device (in this case the video
  7636.  display). CAPS_VERTICAL_RESOLUTION and CAPS_HORIZONTAL_RESOLUTION give the
  7637.  resolution of the output device in the rather ungainly units of pixels per
  7638.  meter.
  7639.  
  7640.  Thus, you can determine the physical dimensions of the output device (in
  7641.  meters) by dividing CAPS_HEIGHT by CAPS_VERTICAL_RESOLUTION and CAPS_WIDTH
  7642.  by CAPS_HORIZONTAL_RESOLUTION. (In most cases, these quotients will be less
  7643.  than 1, so you'll probably want to calculate physical dimensions in
  7644.  something other than meters.) You now have enough information to adjust
  7645.  horizontal and vertical sizes in order to draw square squares and round
  7646.  circles.
  7647.  
  7648.  The CLOCK program in Chapter 10 shows how to use the
  7649.  CAPS_VERTICAL_RESOLUTION and CAPS_HORIZONTAL_RESOLUTION values to draw round
  7650.  graphics objects regardless of the different resolutions of the video
  7651.  display. The clock displayed by this program adjusts its size to fit the
  7652.  window but remains round.
  7653.  
  7654.  Using Metric Units
  7655.  
  7656.  You may also want to draw graphic objects in specific sizes, such as units
  7657.  of a fraction of an inch or millimeters. These are called "metric units."
  7658.  
  7659.  There are a couple of ways to do this. The easy approach (described in the
  7660.  next section) lets GPI do most of the work. But you may prefer to retain
  7661.  control over metric scaling entirely within your program. For example,
  7662.  suppose you want to work in units of 1/10 inch. (These units are called "Low
  7663.  English" because they use English measurements. "High English" units are
  7664.  1/1000 inch.)
  7665.  
  7666.  You first need to obtain the horizontal and vertical resolution of the
  7667.  device:
  7668.  
  7669.    static LONG cxPixelsPerMeter, cyPixelsPerMeter ;
  7670.         ∙
  7671.         ∙
  7672.         ∙
  7673.  
  7674.    DevQueryCaps (hdc, CAPS_HORIZONTAL_RESOLUTION, 1L, &cxPixelsPerMeter) ;
  7675.    DevQueryCaps (hdc, CAPS_VERTICAL_RESOLUTION, 1L,&cyPixelsPerMeter) ;
  7676.  
  7677.  There are 2.54 centimeters to the inch and 100 centimeters to the meter.
  7678.  Thus you can calculate pixels per inch by using the following method:
  7679.  
  7680.    static LONG cxPixelsPerInch, cyPixelsPerInch ;
  7681.         ∙
  7682.         ∙
  7683.         ∙
  7684.  
  7685.    cxPixelsPerInch = (cxPixelsPerMeter * 254 + 5000) / 10000 ;
  7686.    cyPixelsPerInch = (cyPixelsPerMeter * 254 + 5000) / 10000 ;
  7687.  
  7688.  The addition of 5000 before the division gives a rounded result.
  7689.  
  7690.  If you want to set the current position 3 inches from the left and 1-1/2
  7691.  inches from the bottom of your client window, you start by setting ptl.x and
  7692.  ptl.y to these values in units of 1/100 inch:
  7693.  
  7694.    ptl.x = 300 ;
  7695.    ptl.y = 150 ;
  7696.  
  7697.  Now convert these coordinates to pixels:
  7698.  
  7699.    ptl.x = ptl.x * cxPixelsPerInch / 100 ;
  7700.    ptl.y = ptl.y * cyPixelsPerInch / 100 ;
  7701.  
  7702.  Then call the GpiMove function.
  7703.  
  7704.  You can also translate a pixel size or position to Low English units. For
  7705.  example, suppose you want to save cxClient and cyClient in these units.
  7706.  Here's the new WM_SIZE code:
  7707.  
  7708.    case WM_SIZE:
  7709.         cxClient = SHORT1FROMMP (mp2) * 100 / cxPixelsPerInch ;
  7710.         cyClient = SHORT2FROMMP (mp2) * 100 / cyPixelsPerInch ;
  7711.         return 0 ;
  7712.  
  7713.  Page Units
  7714.  
  7715.  Rather than do your own translation between metric units and pixels, you can
  7716.  have GPI translate points for you. This requires that you use a function
  7717.  called GpiSetPS to set "presentation page units," which are the units you
  7718.  specify in GPI functions. GPI converts these page units into "device units,"
  7719.  the normal coordinate system in units of pixels relative to the lower-left
  7720.  corner of the window.
  7721.  
  7722.  To use GpiSetPS, you first define a structure of type SIZEL:
  7723.  
  7724.    SIZEL sizl ;
  7725.  
  7726.  The SIZEL structure has two fields named cx and cy. For our purposes, you
  7727.  can set both of these fields to zero:
  7728.  
  7729.    sizl.cx = 0 ;
  7730.    sizl.cy = 0 ;
  7731.  
  7732.  You then call GpiSetPS:
  7733.  
  7734.    GpiSetPS (hps, &sizl, lPageUnits) ;
  7735.  
  7736.  The last parameter specifies the page units. It can be any of the following
  7737.  seven identifiers:
  7738.  
  7739.     Page Units Identifier      Units
  7740.     PU_PELS                    Pixels
  7741.     PU_ARBITRARY               "Square" Pixels
  7742.     PU_LOMETRIC                0.1 millimeter
  7743.     PU_HIMETRIC                0.01 millimeter
  7744.     PU_LOENGLISH               0.01 inch
  7745.     PU_HIENGLISH               0.001 inch
  7746.     PU_TWIPS                   1/1440 inch
  7747.  
  7748.  By default, page units are set to PU_PELS. Page units of PU_ARBITRARY result
  7749.  in an adjustment so that horizontal units you specify in GPI functions are
  7750.  the same as vertical units. This is a compromise between PU_PELS and the
  7751.  five metric page units. The word "twips" stands for "twentieths of a point,"
  7752.  and refers to a printer's point size, approximately 1/72 inch. Thus 1/20
  7753.  point is 1/1440 inch.
  7754.  
  7755.  Be careful with GpiSetPS: The function resets all attributes of the
  7756.  presentation space to default values. Thus, if you use GpiSetPS, it's best
  7757.  to call it immediately after you obtain a presentation space handle using
  7758.  WinBeginPaint or WinGetPS.
  7759.  
  7760.  The RULER program in Figure 5-9 shows how to use GpiSetPS to draw using Low
  7761.  English units.
  7762.  
  7763.  Figure 5-9.  The RULER program.
  7764.  
  7765.    The RULER File
  7766.  
  7767.    #-----------------
  7768.    # RULER make file
  7769.    #-----------------
  7770.  
  7771.    ruler.obj : ruler.c
  7772.         cl -c -G2sw -W3 ruler.c
  7773.  
  7774.    ruler.exe : ruler.obj ruler.def
  7775.         link ruler, /align:16, NUL, os2, ruler
  7776.  
  7777.    The RULER.C File
  7778.  
  7779.    /*-------------------------
  7780.       RULER.C -- Draw a Ruler
  7781.      -------------------------*/
  7782.  
  7783.    #define INCL_WIN
  7784.    #define INCL_GPI
  7785.    #include <os2.h>
  7786.    #include <stdio.h>
  7787.  
  7788.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  7789.  
  7790.    int main (void)
  7791.         {
  7792.         static CHAR  szClientClass [] = "Ruler" ;
  7793.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  7794.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  7795.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  7796.         HAB          hab ;
  7797.         HMQ          hmq ;
  7798.         HWND         hwndFrame, hwndClient ;
  7799.         QMSG         qmsg ;
  7800.  
  7801.         hab = WinInitialize (0) ;
  7802.         hmq = WinCreateMsgQueue (hab, 0) ;
  7803.  
  7804.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  7805.  
  7806.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  7807.                                         &flFrameFlags, szClientClass, NULL,
  7808.                                         0L, NULL, 0, &hwndClient) ;
  7809.  
  7810.         WinSendMsg (hwndFrame, WM_SETICON,
  7811.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  7812.                     NULL) ;
  7813.  
  7814.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  7815.              WinDispatchMsg (hab, &qmsg) ;
  7816.  
  7817.         WinDestroyWindow (hwndFrame) ;
  7818.         WinDestroyMsgQueue (hmq) ;
  7819.         WinTerminate (hab) ;
  7820.         return 0 ;
  7821.         }
  7822.  
  7823.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  7824.         {
  7825.         static SHORT sTick[16] = { 100, 25, 35, 25, 50, 25, 35, 25,
  7826.                                     70, 25, 35, 25, 50, 25, 35, 25 } ;
  7827.         static SHORT cxClient, cyClient, cxChar, cyChar, cyDesc ;
  7828.         static SIZEL sizl ;
  7829.         CHAR         szBuffer [4] ;
  7830.         FONTMETRICS  fm ;
  7831.         HPS          hps ;
  7832.         POINTL       ptl ;
  7833.         SHORT        sIndex ;
  7834.  
  7835.         switch (msg)
  7836.              {
  7837.              case WM_CREATE:
  7838.                   hps = WinGetPS (hwnd) ;
  7839.                   GpiSetPS (hps, &sizl, PU_LOENGLISH) ;
  7840.  
  7841.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  7842.                   cxChar = (SHORT) fm.lAveCharWidth ;
  7843.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  7844.                   cyDesc = (SHORT) fm.lMaxDescender ;
  7845.  
  7846.                   WinReleasePS (hps) ;
  7847.                   return 0 ;
  7848.  
  7849.              case WM_SIZE:
  7850.                   ptl.x = SHORT1FROMMP (mp2) ;
  7851.                   ptl.y = SHORT2FROMMP (mp2) ;
  7852.  
  7853.                   hps = WinGetPS (hwnd) ;
  7854.                   GpiSetPS (hps, &sizl, PU_LOENGLISH) ;
  7855.                   GpiConvert (hps, CVTC_DEVICE, CVTC_PAGE, 1L, &ptl) ;
  7856.                   WinReleasePS (hps) ;
  7857.  
  7858.                   cxClient = (SHORT) ptl.x ;
  7859.                   cyClient = (SHORT) ptl.y ;
  7860.                   return 0 ;
  7861.  
  7862.              case WM_PAINT:
  7863.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  7864.                   GpiSetPS (hps, &sizl, PU_LOENGLISH) ;
  7865.                   GpiErase (hps) ;
  7866.  
  7867.                   for (sIndex = 0 ; sIndex < 16 * (SHORT) cxClient / 100 ;
  7868.                                     sIndex ++)
  7869.                        {
  7870.                        ptl.x = 100 * sIndex / 16 ;
  7871.                        ptl.y = 0 ;
  7872.                        GpiMove (hps, &ptl) ;
  7873.  
  7874.                        ptl.y = sTick [sIndex % 16] ;
  7875.                        GpiLine (hps, &ptl) ;
  7876.  
  7877.                        if (sIndex % 16 == 0)
  7878.                             {
  7879.                             ptl.x -= cxChar / (sIndex > 160 ? 1 : 2) ;
  7880.                             ptl.y += cyDesc ;
  7881.                             GpiCharStringAt (hps, &ptl,
  7882.                                  (LONG) sprintf (szBuffer, "%d", sIndex / 16),
  7883.                                  szBuffer) ;
  7884.                             }
  7885.                        }
  7886.                   WinEndPaint (hps) ;
  7887.                   return 0 ;
  7888.              }
  7889.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  7890.         }
  7891.  
  7892.    The RULER.DEF File
  7893.  
  7894.    ;----------------------------------
  7895.    ; RULER.DEF module definition file
  7896.    ;----------------------------------
  7897.  
  7898.    NAME           RULER     WINDOWAPI
  7899.  
  7900.    DESCRIPTION    'Draw a Ruler (C) Charles Petzold, 1988'
  7901.    PROTMODE
  7902.    HEAPSIZE       1024
  7903.    STACKSIZE      8192
  7904.    EXPORTS        ClientWndProc
  7905.  
  7906.  
  7907.  RULER draws a ruler with tick marks every 1/16 inch along the bottom of its
  7908.  client window, as shown in Figure 5-10.
  7909.  
  7910.  In RULER, the sizl structure is defined as a static variable and implicitly
  7911.  initialized to zero. ClientWndProc calls GpiSetPS with the PU_LOENGLISH
  7912.  parameter whenever it obtains a presentation space handle, which it does
  7913.  three times: during the WM_CREATE, WM_SIZE, and WM_PAINT messages.
  7914.  
  7915.  During the WM_CREATE message, the program obtains font metrics from the
  7916.  GpiQueryFontMetrics function. Because page units have been set to Low
  7917.  English, these font metrics are in units of 1/100 inch.
  7918.  
  7919.  The Low English page units don't affect nongraphics functions. For example,
  7920.  the WM_SIZE message will continue to report the window size in device units
  7921.  (pixels). For this reason, RULER must convert the window size to Low English
  7922.  units before saving the values in cxClient and cyClient. First, the new
  7923.  window size is saved in a POINTL structure:
  7924.  
  7925.    ptl.x = SHORT1FROMMP (mp2) ;
  7926.    ptl.y = SHORT2FROMMP (mp2) ;
  7927.  
  7928.  Next, RULER gets a presention space handle and sets the page units:
  7929.  
  7930.    hps = WinGetPS (hwnd) ;
  7931.    GpiSetPS (hps, &sizl, PU_LOENGLISH) ;
  7932.  
  7933.  The coordinates of the POINTL structure are converted to page units using
  7934.  GpiConvert, and the presentation space is released:
  7935.  
  7936.    GpiConvert (hps, CVTC_DEVICE, CVTC_PAGE, 1L, &ptl) ;
  7937.    WinReleasePS (hps) ;
  7938.  
  7939.  The second parameter to GpiConvert indicates that the POINTL structure (the
  7940.  last parameter) is in device units. The third parameter is the units to
  7941.  which the POINTL structure should be converted. (You can switch these two
  7942.  parameters to convert from page units to device units.)  The fourth
  7943.  parameter is the number of POINTL structures to be converted, passed as the
  7944.  last parameter. Finally, cxClient and cyClient are saved from the converted
  7945.  points as follows:
  7946.  
  7947.    cxClient = (SHORT) ptl.x ;
  7948.    cyClient = (SHORT) ptl.y ;
  7949.  
  7950.  Thus, by the time the WM_PAINT message is processed, everything the program
  7951.  needs to draw the RULER (the size of the client window and the font metrics)
  7952.  is in units of 1/100 inch. The sTick array (which has the lengths of the
  7953.  ruler tick marks) has also been initialized in Low English units.
  7954.  
  7955.  This discussion of page units only scratches the surface of GPI's various
  7956.  transformation functions. The points you specify in GPI functions are
  7957.  actually in a coordinate system called "world space" and are translated to
  7958.  "model space," then to page units, and then to device coordinates. The
  7959.  GpiSetModelTransformMatrix and GpiSetDefaultViewMatrix functions allow you
  7960.  to perform translation, scaling, and rotation on world coordinates for more
  7961.  complex drawing.
  7962.  
  7963.  
  7964.  GPI Primitive 2: Patterned Areas
  7965.  
  7966.  The second GPI primitive is a pattern that fills an enclosed area. You
  7967.  define the area with a series of lines, and GPI fills it. The GpiBox
  7968.  function also uses a pattern to fill the box interior.
  7969.  
  7970.  Area filling under GPI is not implemented as a "flood fill," such as that
  7971.  available with the PAINT statement in Microsoft's QuickBASIC. In a flood
  7972.  fill, you specify a point that is bounded by one or more existing lines. The
  7973.  graphics system fills the area with a pattern by searching for the boundary
  7974.  lines. Flood fills are possible only on raster output devices that allow the
  7975.  graphics system to read (as well as write) individual pixels. Instead, GPI
  7976.  accumulates the lines that you specify as boundaries to the area and then
  7977.  algorithmically fills the enclosed areas defined by these lines.
  7978.  
  7979.  If you have already experimented with the DRO_FILL and DRO_OUTLINEFILL
  7980.  options of GpiBox, you will have noticed that GPI simply fills the box with
  7981.  a solid color. But that's only because the default area pattern is a solid
  7982.  pattern. Let's look at the other available patterns and the various ways of
  7983.  defining and filling an area under GPI.
  7984.  
  7985.  Selecting the Pattern
  7986.  
  7987.  To select the pattern that GPI uses to fill an area, you call
  7988.  
  7989.    GpiSetPattern (hps, lPattern) ;
  7990.  
  7991.  The lPattern parameter can be any one of the following 19 identifiers
  7992.  beginning with the prefix PATSYM ("pattern symbol"):
  7993.  
  7994.     PATSYM_DEFAULT             PATSYM_VERT
  7995.     PATSYM_DENSE1              PATSYM_HORIZ
  7996.     PATSYM_DENSE2              PATSYM_DIAG1
  7997.     PATSYM_DENSE3              PATSYM_DIAG2
  7998.     PATSYM_DENSE4              PATSYM_DIAG3
  7999.     PATSYM_DENSE5              PATSYM_DIAG4
  8000.     PATSYM_DENSE6              PATSYM_NOSHADE
  8001.     PATSYM_DENSE7              PATSYM_SOLID
  8002.     PATSYM_DENSE8              PATSYM_HALFTONE
  8003.                                PATSYM_BLANK
  8004.  
  8005.  The PATSYM_DEFAULT and PATSYM_SOLID identifiers have the same effect; so do
  8006.  PATSYM_NOSHADE and PATSYM_BLANK. PATSYM_DIAG1 and PATSYM_DIAG2 are patterns
  8007.  composed of diagonal lines from lower left to upper right. For PATSYM_DIAG3
  8008.  and PATSYM_DIAG4, the diagonal lines go from upper left to lower right.
  8009.  
  8010.  The various PATSYM_DENSE identifiers result in shaded patterns:
  8011.  PATSYM_DENSE1 has the highest color density, and PATSYM_DENSE8 has the
  8012.  lowest color density. You can get a 50 percent shading using
  8013.  PATSYM_HALFTONE, which (depending on the output device) may or may not be
  8014.  the same as PATSYM_DENSE4 or PATSYM_DENSE5.
  8015.  
  8016.  The PATTERNS program shown in Figure 5-11 uses the GpiBox function to draw
  8017.  all 19 patterns in its client window.
  8018.  
  8019.  Figure 5-11.  The PATTERNS program.
  8020.  
  8021.    The PATTERNS File
  8022.  
  8023.    #--------------------
  8024.    # PATTERNS make file
  8025.    #--------------------
  8026.  
  8027.    patterns.obj : patterns.c
  8028.         cl -c -G2sw -W3 patterns.c
  8029.  
  8030.    patterns.exe : patterns.obj patterns.def
  8031.         link patterns, /align:16, NUL, os2, patterns
  8032.  
  8033.    The PATTERNS.C File
  8034.  
  8035.    /*---------------------------------
  8036.       PATTERNS.C -- GPI Area Patterns
  8037.      ---------------------------------*/
  8038.  
  8039.    #define INCL_WIN
  8040.    #define INCL_GPI
  8041.    #include <os2.h>
  8042.    #include <string.h>
  8043.  
  8044.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  8045.  
  8046.    int main (void)
  8047.         {
  8048.         static CHAR  szClientClass [] = "Patterns" ;
  8049.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  8050.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  8051.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  8052.         HAB          hab ;
  8053.         HMQ          hmq ;
  8054.         HWND         hwndFrame, hwndClient ;
  8055.         QMSG         qmsg ;
  8056.  
  8057.         hab = WinInitialize (0) ;
  8058.         hmq = WinCreateMsgQueue (hab, 0) ;
  8059.  
  8060.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  8061.  
  8062.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  8063.                                         &flFrameFlags, szClientClass, NULL,
  8064.                                         0L, NULL, 0, &hwndClient) ;
  8065.  
  8066.         WinSendMsg (hwndFrame, WM_SETICON,
  8067.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  8068.                     NULL) ;
  8069.  
  8070.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  8071.              WinDispatchMsg (hab, &qmsg) ;
  8072.  
  8073.         WinDestroyWindow (hwndFrame) ;
  8074.         WinDestroyMsgQueue (hmq) ;
  8075.         WinTerminate (hab) ;
  8076.         return 0 ;
  8077.         }
  8078.  
  8079.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  8080.         {
  8081.         static struct {
  8082.                       LONG lPatternSymbol ;
  8083.                       CHAR *szPatternSymbol ;
  8084.                       }
  8085.                       show [] =
  8086.                       {
  8087.                       PATSYM_DEFAULT  , "PATSYM_DEFAULT"  ,
  8088.                       PATSYM_DENSE1   , "PATSYM_DENSE1"   ,
  8089.                       PATSYM_DENSE2   , "PATSYM_DENSE2"   ,
  8090.                       PATSYM_DENSE3   , "PATSYM_DENSE3"   ,
  8091.                       PATSYM_DENSE4   , "PATSYM_DENSE4"   ,
  8092.                       PATSYM_DENSE5   , "PATSYM_DENSE5"   ,
  8093.                       PATSYM_DENSE6   , "PATSYM_DENSE6"   ,
  8094.                       PATSYM_DENSE7   , "PATSYM_DENSE7"   ,
  8095.                       PATSYM_DENSE8   , "PATSYM_DENSE8"   ,
  8096.                       PATSYM_VERT     , "PATSYM_VERT"     ,
  8097.                       PATSYM_HORIZ    , "PATSYM_HORIZ"    ,
  8098.                       PATSYM_DIAG1    , "PATSYM_DIAG1"    ,
  8099.                       PATSYM_DIAG2    , "PATSYM_DIAG2"    ,
  8100.                       PATSYM_DIAG3    , "PATSYM_DIAG3"    ,
  8101.                       PATSYM_DIAG4    , "PATSYM_DIAG4"    ,
  8102.                       PATSYM_NOSHADE  , "PATSYM_NOSHADE"  ,
  8103.                       PATSYM_SOLID    , "PATSYM_SOLID"    ,
  8104.                       PATSYM_HALFTONE , "PATSYM_HALFTONE" ,
  8105.                       PATSYM_BLANK    , "PATSYM_BLANK"    ,
  8106.                       } ;
  8107.         static SHORT  cxClient, cyClient, cxCaps, cyChar, cyDesc,
  8108.                       sNumTypes = sizeof show / sizeof show[0] ;
  8109.         FONTMETRICS   fm ;
  8110.  
  8111.         HPS           hps ;
  8112.         POINTL        ptl ;
  8113.         SHORT         sIndex ;
  8114.  
  8115.         switch (msg)
  8116.              {
  8117.              case WM_CREATE:
  8118.                   hps = WinGetPS (hwnd) ;
  8119.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  8120.                   cxCaps = (SHORT) fm.lEmInc ;
  8121.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  8122.                   cyDesc = (SHORT) fm.lMaxDescender ;
  8123.                   WinReleasePS (hps) ;
  8124.                   return 0 ;
  8125.  
  8126.              case WM_SIZE:
  8127.                   cxClient = SHORT1FROMMP (mp2) ;
  8128.                   cyClient = SHORT2FROMMP (mp2) ;
  8129.                   return 0 ;
  8130.  
  8131.              case WM_PAINT:
  8132.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  8133.                   GpiErase (hps) ;
  8134.  
  8135.                   for (sIndex = 0 ; sIndex < sNumTypes ; sIndex ++)
  8136.                        {
  8137.                        GpiSetPattern (hps, show [sIndex].lPatternSymbol) ;
  8138.  
  8139.                        ptl.x = (sIndex < 10 ? 1 : 33) * cxCaps ;
  8140.                        ptl.y = cyClient - (sIndex % 10 * 5 + 4) * cyChar / 2
  8141.                                                           + cyDesc ;
  8142.  
  8143.                        GpiCharStringAt (hps, &ptl,
  8144.                              (LONG) strlen (show [sIndex].szPatternSymbol),
  8145.                                   show [sIndex].szPatternSymbol) ;
  8146.  
  8147.                        ptl.x = (sIndex < 10 ? 20 : 52) * cxCaps ;
  8148.                        ptl.y -= cyDesc + cyChar / 2 ;
  8149.                        GpiMove (hps, &ptl) ;
  8150.  
  8151.                        ptl.x += 10 * cxCaps ;
  8152.                        ptl.y += 2 * cyChar ;
  8153.                        GpiBox (hps, DRO_FILL, &ptl, 0L, 0L) ;
  8154.                        }
  8155.  
  8156.                   WinEndPaint (hps) ;
  8157.                   return 0 ;
  8158.              }
  8159.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  8160.         }
  8161.  
  8162.    The PATTERNS.DEF File
  8163.  
  8164.    ;-------------------------------------
  8165.    ; PATTERNS.DEF module definition file
  8166.    ;-------------------------------------
  8167.  
  8168.    NAME           PATTERNS  WINDOWAPI
  8169.  
  8170.    DESCRIPTION    'GPI Area Patterns (C) Charles Petzold, 1988'
  8171.    PROTMODE
  8172.    HEAPSIZE       1024
  8173.    STACKSIZE      8192
  8174.    EXPORTS        ClientWndProc
  8175.  
  8176.  
  8177.  The various patterns drawn by this program are shown in Figure 5-12.
  8178.  
  8179.  A pattern is really only a small rectangular bitmap that is repeated
  8180.  horizontally and vertically to fill an area. In the next chapter I'll show
  8181.  you how to create your own patterns for area filling.
  8182.  
  8183.  Defining an Area
  8184.  
  8185.  Area filling is not limited to the GpiBox function. You can define any area
  8186.  to be filled by simply drawing a series of lines between the GpiBeginArea
  8187.  and GpiEndArea functions:
  8188.  
  8189.    GpiBeginArea (hps, lAreaFlags) ;
  8190.         [draw lines to define the area]
  8191.    GpiEndArea (hps) ;
  8192.  
  8193.  This is known as an "area bracket." GPI does not fill the area until you
  8194.  call the GpiEndArea function.
  8195.  
  8196.  For example, suppose you want to draw a large filled triangle in your client
  8197.  window. If cxClient and cyClient are the dimensions of the client window,
  8198.  here's the code:
  8199.  
  8200.    GpiBeginArea (hps, 0L) ;
  8201.  
  8202.    ptl.x = 0 ;
  8203.    ptl.y = 0 ;
  8204.    GpiMove (hps, &ptl) ;
  8205.  
  8206.    ptl.x = cxClient / 2 ;
  8207.    ptl.y = cyClient ;
  8208.    GpiLine (hps, &ptl) ;
  8209.  
  8210.    ptl.x = cxClient ;
  8211.    ptl.y = 0 ;
  8212.    GpiLine (hps, &ptl) ;
  8213.  
  8214.    ptl.x = 0 ;
  8215.    ptl.y = 0 ;
  8216.    GpiLine (hps, &ptl) ;
  8217.  
  8218.    GpiEndArea (hps) ;
  8219.  
  8220.  The last GpiLine call, which closes the triangle, is not required. If you do
  8221.  not close the figure, GPI will close it for you by drawing a straight line
  8222.  to the starting point.
  8223.  
  8224.  Only a subset of GPI functions are allowed within an area bracket. All line
  8225.  drawing and line attribute functions are allowed, but little else. If you
  8226.  call GpiBox within an area bracket, it should be with the DRO_OUTLINE
  8227.  option. If you want to use a nonsolid pattern, call GpiSetPattern before
  8228.  calling GpiBeginArea.
  8229.  
  8230.  You can define more than one filled area within a single area bracket. When
  8231.  you call GpiMove in an area bracket, GPI closes the previous figure (if
  8232.  necessary) and starts a second figure. For example, the following code draws
  8233.  two filled triangles side by side in the window:
  8234.  
  8235.    GpiBeginArea (hps, 0L) ;
  8236.  
  8237.    ptl.x = 0 ;
  8238.    ptl.y = 0 ;
  8239.    GpiMove (hps, &ptl) ;
  8240.  
  8241.    ptl.x = cxClient / 4 ;
  8242.    ptl.y = cyClient ;
  8243.    GpiLine (hps, &ptl) ;
  8244.  
  8245.    ptl.x = cxClient / 2 ;
  8246.    ptl.y = 0 ;
  8247.    GpiLine (hps, &ptl) ;
  8248.  
  8249.    GpiMove (hps, &ptl) ;
  8250.  
  8251.    ptl.x = 3 * cxClient / 4 ;
  8252.    ptl.y = cyClient ;
  8253.    GpiLine (hps, &ptl) ;
  8254.  
  8255.    ptl.x = cxClient ;
  8256.    ptl.y = 0 ;
  8257.    GpiLine (hps, &ptl) ;
  8258.  
  8259.    GpiEndArea (hps) ;
  8260.  
  8261.  In this case I'm letting GPI close the two triangles. The GpiMove call in
  8262.  the middle of this area bracket marks the beginning of the second triangle,
  8263.  which is the same as the third point of the first triangle. As part of area
  8264.  bracket processing, GPI closes the first triangle with a line from
  8265.  (cxClient/2,0) to (0,0). Similarly, the GpiEndArea call causes GPI to
  8266.  construct a boundary line from (cxClient,0) to (cxClient/2,0).
  8267.  
  8268.  In the preceding examples, the boundary lines are not actually drawn by GPI.
  8269.  GPI uses the lines you specify solely for defining the enclosed area.
  8270.  Whether GPI draws the boundary lines or not is governed by the second
  8271.  parameter to GpiBeginArea. It can be one of the following identifiers:
  8272.  
  8273.    BA_NOBOUNDARY
  8274.    BA_BOUNDARY
  8275.  
  8276.  The BA_NOBOUNDARY identifier is equal to zero, so no boundary line is drawn
  8277.  in the preceding examples. You can also combine these identifiers by using
  8278.  the C bitwise OR operator with one of the following identifiers:
  8279.  
  8280.    BA_ALTERNATE
  8281.    BA_WINDING
  8282.  
  8283.  The BA_ALTERNATE identifier is equal to zero, so that is the default if you
  8284.  use neither identifier. These identifiers govern whether GPI uses
  8285.  "alternate" or "winding" mode to fill areas.
  8286.  
  8287.  Alternate and Winding Modes
  8288.  
  8289.  When you draw a series of lines to define a filled area, the lines can cross
  8290.  each other, and the enclosed area can actually comprise several smaller
  8291.  sub-areas. You may not want all of these areas to be filled. The classic
  8292.  example is a five-pointed star that you draw with five lines. The points of
  8293.  the star and the interior pentagon are all sub-areas. You can have GPI fill
  8294.  that interior pentagon by specifying winding mode or leave it unfilled by
  8295.  specifying alternate mode. This is illustrated in the STARFILL program in
  8296.  Figure 5-13.
  8297.  
  8298.  Figure 5-13.  The STARFILL program.
  8299.  
  8300.    The STARFILL File
  8301.  
  8302.    #--------------------
  8303.    # STARFILL make file
  8304.    #--------------------
  8305.  
  8306.    starfill.obj : starfill.c
  8307.         cl -c -G2sw -W3 starfill.c
  8308.  
  8309.    starfill.exe : starfill.obj starfill.def
  8310.         link starfill, /align:16, NUL, os2, starfill
  8311.  
  8312.    The STARFILL.C File
  8313.  
  8314.    /*-------------------------------------------
  8315.       STARFILL.C -- Alternate and Winding Modes
  8316.     --------------------------------------------*/
  8317.  
  8318.    #define INCL_GPI
  8319.    #include <os2.h>
  8320.  
  8321.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  8322.  
  8323.    int main (void)
  8324.         {
  8325.         static CHAR  szClientClass [] = "StarFill" ;
  8326.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  8327.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  8328.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  8329.         HAB          hab ;
  8330.         HMQ          hmq ;
  8331.         HWND         hwndFrame, hwndClient ;
  8332.         QMSG         qmsg ;
  8333.  
  8334.         hab = WinInitialize (0) ;
  8335.         hmq = WinCreateMsgQueue (hab, 0) ;
  8336.  
  8337.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  8338.  
  8339.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  8340.                                         &flFrameFlags, szClientClass,
  8341.                                         " - Alternate and Winding",
  8342.                                         0L, NULL, 0, &hwndClient) ;
  8343.  
  8344.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  8345.              WinDispatchMsg (hab, &qmsg) ;
  8346.  
  8347.         WinDestroyWindow (hwndFrame) ;
  8348.         WinDestroyMsgQueue (hmq) ;
  8349.         WinTerminate (hab) ;
  8350.         return 0 ;
  8351.         }
  8352.  
  8353.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  8354.         {
  8355.         static POINTL aptlStar[5] = {-59,-81, 0,100, 59,-81, -95,31, 95,31 } ;
  8356.         static SHORT  cxClient, cyClient ;
  8357.         HPS           hps ;
  8358.  
  8359.         POINTL        aptl[5] ;
  8360.         SHORT         sIndex ;
  8361.  
  8362.         switch (msg)
  8363.              {
  8364.              case WM_SIZE:
  8365.                   cxClient = SHORT1FROMMP (mp2) ;
  8366.                   cyClient = SHORT2FROMMP (mp2) ;
  8367.                   return 0 ;
  8368.  
  8369.              case WM_PAINT:
  8370.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  8371.                   GpiErase (hps) ;
  8372.                   GpiSetPattern (hps, PATSYM_HALFTONE) ;
  8373.  
  8374.                             /*---------------------
  8375.                                Alternate Fill Mode
  8376.                               ---------------------*/
  8377.  
  8378.                   for (sIndex = 0 ; sIndex < 5 ; sIndex++)
  8379.                        {
  8380.                        aptl[sIndex].x = cxClient / 4 + cxClient *
  8381.                                              aptlStar[sIndex].x / 400 ;
  8382.                        aptl[sIndex].y = cyClient / 2 + cyClient *
  8383.                                              aptlStar[sIndex].y / 200 ;
  8384.                        }
  8385.  
  8386.                   GpiBeginArea (hps, BA_NOBOUNDARY | BA_ALTERNATE) ;
  8387.                   GpiMove (hps, aptl) ;
  8388.                   GpiPolyLine (hps, 4L, aptl + 1) ;
  8389.                   GpiEndArea (hps) ;
  8390.  
  8391.                             /*-------------------
  8392.                                Winding Fill Mode
  8393.                               -------------------*/
  8394.  
  8395.                   for (sIndex = 0 ; sIndex < 5 ; sIndex++)
  8396.                        aptl[sIndex].x += cxClient / 2 ;
  8397.  
  8398.                   GpiBeginArea (hps, BA_NOBOUNDARY | BA_WINDING) ;
  8399.                   GpiMove (hps, aptl) ;
  8400.                   GpiPolyLine (hps, 4L, aptl + 1) ;
  8401.                   GpiEndArea (hps) ;
  8402.  
  8403.                   WinEndPaint (hps) ;
  8404.                   return 0 ;
  8405.              }
  8406.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  8407.         }
  8408.  
  8409.    The STARFILL.DEF File
  8410.  
  8411.    ;-------------------------------------
  8412.    ; STARFILL.DEF module definition file
  8413.    ;-------------------------------------
  8414.  
  8415.    NAME           STARFILL  WINDOWAPI
  8416.  
  8417.    DESCRIPTION    'Alternate and Winding Modes (C) Charles Petzold, 1988'
  8418.    PROTMODE
  8419.    HEAPSIZE       1024
  8420.    STACKSIZE      8192
  8421.    EXPORTS        ClientWndProc
  8422.  
  8423.  
  8424.  As you can see in Figure 5-14, the center of the five-pointed star is
  8425.  filled in winding mode but not in alternate mode.
  8426.  
  8427.  At first, the difference between alternate and winding modes seems rather
  8428.  simple. For alternate mode, you can imagine a line drawn from a point in an
  8429.  enclosed area to infinity. The enclosed area is filled only if that
  8430.  imaginary line crosses an odd number of boundary lines. This is why the
  8431.  points of the star are filled but the center is not.
  8432.  
  8433.  The example of the five-pointed star makes winding mode seem simpler than it
  8434.  actually is. When you're drawing a single object in an area bracket, in most
  8435.  cases winding mode will cause all enclosed areas to be filled. But there are
  8436.  exceptions.
  8437.  
  8438.  To determine whether an enclosed area is filled in winding mode, you again
  8439.  imagine a line drawn from a point in that area to infinity:
  8440.  
  8441.    ■  If the imaginary line crosses an odd number of boundary lines, the area
  8442.       is filled, just as in alternate mode.
  8443.  
  8444.    ■  If the imaginary line crosses an even number of boundary lines, the
  8445.       area can be either filled or not filled. The area is filled if the
  8446.       number of boundary lines going in one direction (relative to the
  8447.       imaginary line) is not equal to the number of boundary lines going in
  8448.       the other direction.
  8449.  
  8450.  For example, consider the object shown in Figure 5-15.
  8451.  
  8452.  Figure 5-15.  A figure in which winding mode does not fill all interior
  8453.                areas.
  8454.  
  8455.                                    ┌─────────────────┐
  8456.                                    │                  │
  8457.                                    │             2    │
  8458.                                    │                  │
  8459.                  ┌────────────────┼────────┐         
  8460.                  │                 │        │         │
  8461.                  │    1                4   │         │
  8462.                  │                 │        │         │
  8463.                          ┌────────┼────────┼────────┘
  8464.                  │        │        │        │
  8465.                  │        │   5    │        │
  8466.                  │        │        │        │
  8467.                  └───────┼────────┘        
  8468.                           │                 │
  8469.                           │           3     │
  8470.                           │                 │
  8471.                           └────────────────┘
  8472.  
  8473.  
  8474.  The arrows on the lines indicate the direction in which the lines are drawn.
  8475.  Both winding mode and alternate mode will fill the three enclosed L-shaped
  8476.  areas numbered 1 through 3. The two smaller interior areas, numbered 4 and
  8477.  5, will not be filled in alternate mode. But in winding mode, area number 5
  8478.  is filled because you must cross two lines going in the same direction to
  8479.  get from the inside of that area to the outside of the figure. Area number 4
  8480.  is not filled. You must again cross two lines, but the two lines go in
  8481.  opposite directions.
  8482.  
  8483.  Is GPI really smart enough to figure this out? Sure it is, and the ALTWIND
  8484.  program shown in Figure 5-16 demonstrates it.
  8485.  
  8486.  Figure 5-16.  The ALTWIND program.
  8487.  
  8488.    The ALTWIND File
  8489.  
  8490.    #-------------------
  8491.    # ALTWIND make file
  8492.    #-------------------
  8493.  
  8494.    altwind.obj : altwind.c
  8495.         cl -c -G2sw -W3 altwind.c
  8496.  
  8497.    altwind.exe : altwind.obj altwind.def
  8498.         link altwind, /align:16, NUL, os2, altwind
  8499.  
  8500.    The ALTWIND.C File
  8501.  
  8502.    /*------------------------------------------
  8503.       ALTWIND.C -- Alternate and Winding Modes
  8504.     -------------------------------------------*/
  8505.  
  8506.    #define INCL_GPI
  8507.    #include <os2.h>
  8508.  
  8509.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  8510.  
  8511.    int main (void)
  8512.         {
  8513.         static CHAR  szClientClass [] = "AltWind" ;
  8514.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  8515.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  8516.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  8517.         HAB          hab ;
  8518.         HMQ          hmq ;
  8519.         HWND         hwndFrame, hwndClient ;
  8520.         QMSG         qmsg ;
  8521.  
  8522.         hab = WinInitialize (0) ;
  8523.         hmq = WinCreateMsgQueue (hab, 0) ;
  8524.  
  8525.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  8526.  
  8527.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  8528.                                         &flFrameFlags, szClientClass, NULL,
  8529.                                         0L, NULL, 0, &hwndClient) ;
  8530.  
  8531.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  8532.              WinDispatchMsg (hab, &qmsg) ;
  8533.  
  8534.         WinDestroyWindow (hwndFrame) ;
  8535.         WinDestroyMsgQueue (hmq) ;
  8536.         WinTerminate (hab) ;
  8537.         return 0 ;
  8538.         }
  8539.  
  8540.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  8541.         {
  8542.         static POINTL aptlFigure[10] = { 10,30, 50,30, 50,90, 90,90, 90,50,
  8543.                                          30,50, 30,10, 70,10, 70,70, 10,70 } ;
  8544.         static SHORT  cxClient, cyClient ;
  8545.         HPS           hps ;
  8546.         POINTL        aptl[10] ;
  8547.         SHORT         sIndex ;
  8548.  
  8549.         switch (msg)
  8550.              {
  8551.              case WM_SIZE:
  8552.                   cxClient = SHORT1FROMMP (mp2) ;
  8553.                   cyClient = SHORT2FROMMP (mp2) ;
  8554.                   return 0 ;
  8555.  
  8556.              case WM_PAINT:
  8557.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  8558.                   GpiErase (hps) ;
  8559.                   GpiSetPattern (hps, PATSYM_HALFTONE) ;
  8560.  
  8561.                             /*---------------------
  8562.                                Alternate Fill Mode
  8563.                               ---------------------*/
  8564.  
  8565.                   for (sIndex = 0 ; sIndex < 10 ; sIndex++)
  8566.                        {
  8567.                        aptl[sIndex].x = cxClient * aptlFigure[sIndex].x / 200
  8568.                        aptl[sIndex].y = cyClient * aptlFigure[sIndex].y / 100
  8569.                        }
  8570.  
  8571.                   GpiBeginArea (hps, BA_BOUNDARY | BA_ALTERNATE) ;
  8572.                   GpiMove (hps, aptl) ;
  8573.                   GpiPolyLine (hps, 9L, aptl + 1) ;
  8574.                   GpiEndArea (hps) ;
  8575.  
  8576.                             /*-------------------
  8577.                                Winding Fill Mode
  8578.                               -------------------*/
  8579.  
  8580.                   for (sIndex = 0 ; sIndex < 10 ; sIndex++)
  8581.                        aptl[sIndex].x += cxClient / 2 ;
  8582.  
  8583.                   GpiBeginArea (hps, BA_BOUNDARY | BA_WINDING) ;
  8584.                   GpiMove (hps, aptl) ;
  8585.                   GpiPolyLine (hps, 9L, aptl + 1) ;
  8586.                   GpiEndArea (hps) ;
  8587.  
  8588.                   WinEndPaint (hps) ;
  8589.                   return 0 ;
  8590.              }
  8591.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  8592.         }
  8593.  
  8594.    The ALTWIND.DEF File
  8595.  
  8596.    ;------------------------------------
  8597.    ; ALTWIND.DEF module definition file
  8598.    ;------------------------------------
  8599.  
  8600.    NAME           ALTWIND   WINDOWAPI
  8601.  
  8602.    DESCRIPTION    'Alternate and Winding Modes (C) Charles Petzold, 1988'
  8603.    PROTMODE
  8604.    HEAPSIZE       1024
  8605.    STACKSIZE      8192
  8606.    EXPORTS        ClientWndProc
  8607.  
  8608.  
  8609.  Figure 5-17 shows the ALTWIND display.
  8610.  
  8611.  If you use GpiBox with the DRO_OUTLINE option within an area bracket, you
  8612.  need to know how GPI draws the box in order to anticipate how the
  8613.  intersection of the box and other closed objects will be filled in winding
  8614.  mode. GPI begins drawing the box at the current position. The first line it
  8615.  draws is horizontal, and then the box is continued from there. Thus, if the
  8616.  current position is the lower-left or upper-right corner of the box, the box
  8617.  is drawn counterclockwise.
  8618.  
  8619.  Color and Mix
  8620.  
  8621.  All text, lines, and areas we've drawn have appeared on the window in black
  8622.  on a white background. Or maybe not: If you have set different "window
  8623.  background" and "window text" colors using the Presentation Manager Control
  8624.  Panel program, GPI uses these colors for the background of the window and
  8625.  the color of text, lines, areas, and other primitives.
  8626.  
  8627.  The Color Index
  8628.  You specify a color by calling the GpiSetColor function:
  8629.  
  8630.    GpiSetColor (hps, lColorIndex) ;
  8631.  
  8632.  The color is an attribute of the presentation space. The color you set with
  8633.  GpiSetColor affects all subsequent GPI primitives until you change the color
  8634.  again or release the presentation space.
  8635.  
  8636.  Generally, the lColorIndex value will be one of the following identifiers:
  8637.  
  8638.     CLR_BACKGROUND             CLR_DARKGRAY
  8639.     CLR_BLUE                   CLR_DARKBLUE
  8640.     CLR_RED                    CLR_DARKRED
  8641.     CLR_PINK                   CLR_DARKPINK
  8642.     CLR_GREEN                  CLR_DARKGREEN
  8643.     CLR_CYAN                   CLR_DARKCYAN
  8644.     CLR_YELLOW                 CLR_BROWN
  8645.     CLR_NEUTRAL                CLR_PALEGRAY
  8646.  
  8647.  Each of these color index identifiers is self-explanatory, with the
  8648.  exception of CLR_BACKGROUND and CLR_NEUTRAL:
  8649.  
  8650.    ■  CLR_BACKGROUND is the color you set in the Presentation Manager Control
  8651.       Panel program as the "window background" color. By default, this is
  8652.       white. The GpiErase function erases a window using this CLR_BACKGROUND
  8653.       color.
  8654.  
  8655.    ■  CLR_NEUTRAL is the "window text" color you set in the Control Panel
  8656.       Program; by default, CLR_NEUTRAL is black. For a new presentation
  8657.       space, all GPI primitives you draw will use the CLR_NEUTRAL color. GPI
  8658.       also recognizes the CLR_DEFAULT identifier, which has the same effect
  8659.       as CLR_NEUTRAL when used with GpiSetColor.
  8660.  
  8661.  In one sense, these interpretations of the CLR_BACKGROUND and the
  8662.  CLR_NEUTRAL identifiers are convenient for the programmer. The user has
  8663.  selected these colors as his or her personal preferences for window
  8664.  background and foreground colors. A Presentation Manager program uses these
  8665.  colors by default. A user's preference, however, can sometimes defeat a
  8666.  feature of a program. For example, if your program uses CLR_RED text for
  8667.  emphasis, the text won't stand out if the user has selected red as the
  8668.  normal window text color.
  8669.  
  8670.  Therefore, GPI lets you override the user's preferences and explicitly set
  8671.  all colors used by the program. GPI provides two additional color indexes
  8672.  for specifying black and white:
  8673.  
  8674.     CLR_BLACK                 CLR_WHITE
  8675.  
  8676.  Another pair of color indexes is more appropriate for use with bitmaps:
  8677.  
  8678.     CLR_FALSE                 CLR_TRUE
  8679.  
  8680.  On a video display, CLR_FALSE has the same effect as CLR_BLACK, and CLR_TRUE
  8681.  has the same effect as CLR_WHITE; on a printer, this relationship is
  8682.  reversed because video displays are black background devices and printers
  8683.  are white background devices.
  8684.  
  8685.  You may be familiar with the IRGB (Intensity-Red-Green-Blue) color encoding
  8686.  of the IBM CGA, EGA, and VGA video adapters. The table below shows how it
  8687.  corresponds to the GPI color indexes.
  8688.  
  8689.     I   R   G   B   IRGB Color     Equivalent Color Index
  8690.                     Name
  8691.     0   0   0   0   Black          CLR_BLACK
  8692.     0   0   0   1   Blue           CLR_DARKBLUE
  8693.     0   0   1   0   Green          CLR_DARKGREEN
  8694.     0   0   1   1   Cyan           CLR_DARKCYAN
  8695.     0   1   0   0   Red            CLR_DARKRED
  8696.     0   1   0   1   Magenta        CLR_DARKPINK
  8697.     0   1   1   0   Brown          CLR_BROWN
  8698.     0   1   1   1   Light Gray     CLR_PALEGRAY
  8699.     1   0   0   0   Dark Gray      CLR_DARKGRAY
  8700.     1   0   0   1   Light Blue     CLR_BLUE
  8701.     1   0   1   0   Light Green    CLR_GREEN
  8702.     1   0   1   1   Light Cyan     CLR_CYAN
  8703.     1   1   0   0   Light Red      CLR_RED
  8704.     1   1   0   1   Light Magenta  CLR_PINK
  8705.     1   1   1   0   Yellow         CLR_YELLOW
  8706.     1   1   1   1   White          CLR_WHITE
  8707.  
  8708.  In literature about the IBM video adapters, "magenta" is often used to
  8709.  describe the color known as "pink" in GPI. In technical literature on the
  8710.  CGA, EGA, and VGA boards, colors with the I (intensity) bit set are
  8711.  traditionally referred to as "light" colors; by contrast, in GPI, most
  8712.  colors without the I bit set are "dark" or "pale" colors.
  8713.  
  8714.  The COLORS program shown in Figure 5-18 displays the colors available with
  8715.  all 21 CLR identifiers:
  8716.  
  8717.  Figure 5-18.  The COLORS program.
  8718.  
  8719.    The COLORS File
  8720.  
  8721.    #------------------
  8722.    # COLORS make file
  8723.    #------------------
  8724.  
  8725.    colors.obj : colors.c
  8726.         cl -c -G2sw -W3 colors.c
  8727.  
  8728.    colors.exe : colors.obj colors.def
  8729.         link colors, /align:16, NUL, os2, colors
  8730.  
  8731.    The COLORS.C File
  8732.  
  8733.    /*-----------------------------------
  8734.       COLORS.C -- GPI Foreground Colors
  8735.      -----------------------------------*/
  8736.  
  8737.    #define INCL_WIN
  8738.    #define INCL_GPI
  8739.    #include <os2.h>
  8740.    #include <string.h>
  8741.  
  8742.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  8743.  
  8744.    int main (void)
  8745.         {
  8746.         static CHAR  szClientClass [] = "Colors" ;
  8747.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  8748.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  8749.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  8750.         HAB          hab ;
  8751.         HMQ          hmq ;
  8752.         HWND         hwndFrame, hwndClient ;
  8753.         QMSG         qmsg ;
  8754.  
  8755.         hab = WinInitialize (0) ;
  8756.         hmq = WinCreateMsgQueue (hab, 0) ;
  8757.  
  8758.         WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;
  8759.  
  8760.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  8761.                                         &flFrameFlags, szClientClass, NULL,
  8762.                                         0L, NULL, 0, &hwndClient) ;
  8763.  
  8764.         WinSendMsg (hwndFrame, WM_SETICON,
  8765.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  8766.                     NULL) ;
  8767.  
  8768.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  8769.              WinDispatchMsg (hab, &qmsg) ;
  8770.  
  8771.         WinDestroyWindow (hwndFrame) ;
  8772.         WinDestroyMsgQueue (hmq) ;
  8773.         WinTerminate (hab) ;
  8774.         return 0 ;
  8775.         }
  8776.  
  8777.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  8778.         {
  8779.         static struct {
  8780.                       LONG lColorIndex ;
  8781.                       CHAR *szColorIndex ;
  8782.                       }
  8783.                       show [] =
  8784.                       {
  8785.                       CLR_FALSE      , "CLR_FALSE"      ,
  8786.                       CLR_TRUE       , "CLR_TRUE"       ,
  8787.                       CLR_DEFAULT    , "CLR_DEFAULT"    ,
  8788.                       CLR_WHITE      , "CLR_WHITE"      ,
  8789.                       CLR_BLACK      , "CLR_BLACK"      ,
  8790.                       CLR_BACKGROUND , "CLR_BACKGROUND" ,
  8791.                       CLR_BLUE       , "CLR_BLUE"       ,
  8792.                       CLR_RED        , "CLR_RED"        ,
  8793.                       CLR_PINK       , "CLR_PINK"       ,
  8794.                       CLR_GREEN      , "CLR_GREEN"      ,
  8795.                       CLR_CYAN       , "CLR_CYAN"       ,
  8796.                       CLR_YELLOW     , "CLR_YELLOW"     ,
  8797.                       CLR_NEUTRAL    , "CLR_NEUTRAL"    ,
  8798.                       CLR_DARKGRAY   , "CLR_DARKGRAY"   ,
  8799.                       CLR_DARKBLUE   , "CLR_DARKBLUE"   ,
  8800.                       CLR_DARKRED    , "CLR_DARKRED"    ,
  8801.                       CLR_DARKPINK   , "CLR_DARKPINK"   ,
  8802.                       CLR_DARKGREEN  , "CLR_DARKGREEN"  ,
  8803.                       CLR_DARKCYAN   , "CLR_DARKCYAN"   ,
  8804.                       CLR_BROWN      , "CLR_BROWN"      ,
  8805.                       CLR_PALEGRAY   , "CLR_PALEGRAY"
  8806.                       } ;
  8807.         static SHORT  cxClient, cyClient, cxCaps, cyChar, cyDesc,
  8808.                       sNumColors = sizeof show / sizeof show[0] ;
  8809.         FONTMETRICS   fm ;
  8810.         HPS           hps ;
  8811.         POINTL        ptl ;
  8812.         SHORT         sIndex ;
  8813.  
  8814.         switch (msg)
  8815.              {
  8816.              case WM_CREATE:
  8817.                   hps = WinGetPS (hwnd) ;
  8818.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  8819.                   cxCaps = (SHORT) fm.lEmInc ;
  8820.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  8821.  
  8822.                   cyDesc = (SHORT) fm.lMaxDescender ;
  8823.                   WinReleasePS (hps) ;
  8824.                   return 0 ;
  8825.  
  8826.              case WM_SIZE:
  8827.                   cxClient = SHORT1FROMMP (mp2) ;
  8828.                   cyClient = SHORT2FROMMP (mp2) ;
  8829.                   return 0 ;
  8830.  
  8831.              case WM_PAINT:
  8832.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  8833.                   GpiErase (hps) ;
  8834.  
  8835.                   for (sIndex = 0 ; sIndex < sNumColors ; sIndex ++)
  8836.                        {
  8837.                        ptl.x = (sIndex < 11 ? 1 : 33) * cxCaps ;
  8838.                        ptl.y = cyClient - (sIndex % 11 * 5 + 4) * cyChar / 2
  8839.                                                           + cyDesc ;
  8840.  
  8841.                        GpiCharStringAt (hps, &ptl,
  8842.                              (LONG) strlen (show [sIndex].szColorIndex),
  8843.                                   show [sIndex].szColorIndex) ;
  8844.  
  8845.                        ptl.x = (sIndex < 11 ? 20 : 52) * cxCaps ;
  8846.                        ptl.y -= cyDesc + cyChar / 2 ;
  8847.                        GpiMove (hps, &ptl) ;
  8848.  
  8849.                        GpiSavePS (hps) ;
  8850.                        GpiSetColor (hps, show [sIndex].lColorIndex) ;
  8851.  
  8852.                        ptl.x += 10 * cxCaps ;
  8853.                        ptl.y += 2 * cyChar ;
  8854.                        GpiBox (hps, DRO_FILL, &ptl, 0L, 0L) ;
  8855.  
  8856.                        GpiRestorePS (hps, -1L) ;
  8857.                        }
  8858.                   WinEndPaint (hps) ;
  8859.                   return 0 ;
  8860.              }
  8861.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  8862.         }
  8863.  
  8864.    The COLORS.DEF File
  8865.  
  8866.    ;-----------------------------------
  8867.    ; COLORS.DEF module definition file
  8868.    ;-----------------------------------
  8869.  
  8870.    NAME           COLORS    WINDOWAPI
  8871.  
  8872.    DESCRIPTION    'GPI Foreground Colors (C) Charles Petzold, 1988'
  8873.    PROTMODE
  8874.    HEAPSIZE       1024
  8875.    STACKSIZE      8192
  8876.    EXPORTS        ClientWndProc
  8877.  
  8878.  
  8879.  This program sets the color by calling GpiSetColor and then calls the GpiBox
  8880.  function to draw a solid rectangle using that color.
  8881.  
  8882.  The GpiSetColor function sets the color for all GPI primitives, including
  8883.  text. How does COLORS prevent the text from appearing in color? Very simple:
  8884.  COLORS calls the GpiSavePS function before calling GpiSetColor and calls
  8885.  GpiRestorePS after calling GpiBox. The GpiSavePS function saves all the
  8886.  attributes of the presentation space and GpiRestorePS restores them. If you
  8887.  remove these two functions from COLORS, you'll find that the text displayed
  8888.  by GpiCharStringAt will also appear in various colors.
  8889.  
  8890.  I could have simplified COLORS a little by using the WinFillRect function:
  8891.  
  8892.    WinFillRect (hps, &rcl, lColorIndex) ;
  8893.  
  8894.  The second parameter is a pointer to a RECTL structure. The function fills
  8895.  that rectangle with the specified color. The WinFillRect function is useful
  8896.  for coloring the background of a client window without calling GpiErase:
  8897.  
  8898.    WinQueryWindowRect (hps, &rcl) ;
  8899.    WinFillRect (hps, &rcl, CLR_CYAN) ;
  8900.  
  8901.  WinFillRect is one of the few drawing functions that begins with a Win
  8902.  prefix rather than Gpi. (You encountered another of these functions──
  8903.  WinDrawText──in Chapter 2.) These are high-level drawing functions that do
  8904.  the work of several GPI functions. They are often convenient but can be used
  8905.  only on a video display. Another useful high-level drawing function is
  8906.  WinDrawBorder.
  8907.  
  8908.  Foreground Mix Mode
  8909.  The use of color may seem fairly straightforward, but it's not. In general,
  8910.  GPI does not simply draw a color on the display. Instead, GPI performs a
  8911.  bitwise operation between the foreground color of the image you're drawing
  8912.  (the source color) and the color already on the surface of the display (the
  8913.  destination color). This operation is called the "mix mode" or simply the
  8914.  "mix."
  8915.  
  8916.  Let's approach this by thinking about a monochrome video display that is
  8917.  capable of two colors: black and white. Each pixel on the display can be
  8918.  represented by either 0 (black) or 1 (white).
  8919.  
  8920.  You want to draw a pixel on this display. This source pixel can be 0 or 1.
  8921.  The surface of the display where you want to draw this pixel (the
  8922.  destination) can also be either 0 or 1. The resultant color of the drawn
  8923.  pixel is defined by the mix mode.
  8924.  
  8925.  There are 16 possible mix modes. These are represented by identifiers
  8926.  defined in PMGPI.H that begin with FM ("foreground mix"). The following
  8927.  table uses C notation to show the bitwise combinations of pixels:
  8928.  
  8929.     Source (SRC): 0   0   1   1
  8930.     Destination   0   1   0   1   Operation         Mix Mode
  8931.     (DEST):
  8932.     Result:       0   0   0   0   0                 FM_ZERO
  8933.                   0   0   0   1   SRC & DEST        FM_AND
  8934.                   0   0   1   0   SRC & ~DEST       FM_MASKSRCNOT
  8935.                   0   0   1   1   SRC               FM_OVERPAINT
  8936.                   0   1   0   0   ~SRC & DEST       FM_SUBTRACT
  8937.                   0   1   0   1   DEST              FM_LEAVEALONE
  8938.                   0   1   1   0   SRC ^ DEST        FM_XOR
  8939.                   0   1   1   1   SRC | DEST        FM_OR
  8940.                   1   0   0   0   ~(SRC | DEST)     FM_NOTMERGESRC
  8941.                   1   0   0   1   ~(SRC ^ DEST)     FM_NOTXORSRC
  8942.                   1   0   1   0   ~DEST             FM_INVERT
  8943.                   1   0   1   1   SRC | ~DEST       FM_MERGESRCNOT
  8944.                   1   1   0   0   ~SRC              FM_NOTCOPYSRC
  8945.                   1   1   0   1   ~SRC | DEST       FM_MERGENOTSRC
  8946.                   1   1   1   0   ~(SRC & DEST)     FM_NOTMASKSRC
  8947.                   1   1   1   1   1                 FM_ONE
  8948.  
  8949.  You can change the mix mode by calling the following function:
  8950.  
  8951.    GpiSetMix (hps, lMixMode)
  8952.  
  8953.  where lMixMode is one of the FM identifiers shown in the table. The default
  8954.  mix mode is FM_OVERPAINT, which transfers the color specified by the
  8955.  GpiSetColor to the destination regardless of the color of the destination.
  8956.  This is what we intuitively expect to happen. The PMGPI.H header file also
  8957.  includes the identifier FM_DEFAULT, which has the same effect as
  8958.  FM_OVERPAINT.
  8959.  
  8960.  If the mix mode is set to FM_XOR, the resulting pixel will be white (1) only
  8961.  if either the source and destination pixels (but not both) were also white.
  8962.  That is, the FM_XOR mix mode causes source pixels of 1 to invert the
  8963.  destination and source pixels of 0 to leave it unchanged:
  8964.  
  8965.    ■  If you set color to CLR_BLACK and the mix mode to FM_XOR, any lines you
  8966.       draw on a black background will be black, and any lines you draw on a
  8967.       white background will be white.
  8968.  
  8969.    ■  If you set color to CLR_WHITE and the mix mode to FM_XOR, any lines you
  8970.       draw on a black background will be white; any lines you draw on a white
  8971.       background will be black.
  8972.  
  8973.  With color, the situation gets just a little more complex. Consider the EGA
  8974.  and VGA display adapters in high-resolution graphics mode. These adapters
  8975.  use 4 bits (intensity, red, green, and blue) for each pixel. The mix mode
  8976.  works on each of these bits individually. For example, if a window is
  8977.  colored with CLR_RED, the surface of the window has its intensity and red
  8978.  bits set to 1 and its blue and green bits set to 0. If you set color to
  8979.  CLR_BLUE, the intensity and blue bits are set to 1, and the green and red
  8980.  bits are set to 0. You use the FM_XOR mix mode and display a line. The text
  8981.  is displayed in CLR_DARKPINK. The resultant red and blue bits are set to 1,
  8982.  and the intensity and green bits are set to 0.
  8983.  
  8984.  The FM_ZERO mix mode causes the GPI primitive you draw to be displayed in
  8985.  black regardless of the destination color and the color you set with
  8986.  GpiSetColor. Similarly, FM_ONE causes a GPI primitive to be displayed in
  8987.  white. The FM_LEAVEALONE mix causes the GPI primitive to be invisible.
  8988.  
  8989.  The FM_INVERT mix mode causes a GPI primitive to invert the color of the
  8990.  destination regardless of the color you set. For example, text drawn on a
  8991.  CLR_RED destination is displayed as CLR_DARKCYAN. FM_INVERT is useful for
  8992.  drawing and erasing an object. When you draw the same object a second time,
  8993.  the destination reverts to its original color. This technique is used in the
  8994.  WEB program in Chapter 9.
  8995.  
  8996.  The Background Color and Mix
  8997.  GPI also has two functions for setting the background color and mix:
  8998.  
  8999.    GpiSetBackColor (hps, lColorIndex) ;
  9000.  
  9001.  and
  9002.  
  9003.    GpiSetBackMix (hps, lMixMode) ;
  9004.  
  9005.  Use of the CLR_DEFAULT as a parameter to GpiSetBackColor has the same effect
  9006.  as CLR_BACKGROUND. For the lMixMode parameter to GpiSetBackMix, you use
  9007.  identifiers beginning with BM ("background mix") rather than FM. Not all mix
  9008.  modes are supported for background mixing. The supported background mixes
  9009.  are shown in the following table:
  9010.  
  9011.     Source (SRC): 0   0   1   1
  9012.     Destination   0   1   0   1   Operation   Mix Mode
  9013.     (DEST):
  9014.     Result:       0   0   1   1   SRC         BM_OVERPAINT
  9015.                   0   1   0   1   DEST        BM_LEAVEALONE
  9016.                   0   1   1   0   SRC ^ DEST  BM_XOR
  9017.                   0   1   1   1   SRC | DEST  BM_OR
  9018.  
  9019.  The default background mix is BM_LEAVEALONE. (BM_DEFAULT provides the same
  9020.  result.) If you want to use a background color, you'll have to change the
  9021.  background mix to something other than BM_LEAVEALONE. Otherwise, GPI will
  9022.  ignore the background color.
  9023.  
  9024.  The background color and mix don't affect lines, but do affect patterns.
  9025.  You'll note that many of the patterns are composed of lines or dots. These
  9026.  lines and dots are drawn on the display using the foreground color and
  9027.  foreground mix mode. The area between the lines and dots is drawn on the
  9028.  display using the background color and background mix mode.
  9029.  
  9030.  For example, suppose you make the following series of function calls:
  9031.  
  9032.    GpiSetColor (hps, CLR_BLUE) ;
  9033.    GpiSetMix (hps, FM_OVERPAINT) ;
  9034.    GpiSetBackColor (hps, CLR_RED) ;
  9035.    GpiSetBackMix (hps, BM_OVERPAINT) ;
  9036.    GpiSetPattern (hps, PATSYM_VERT) ;
  9037.  
  9038.  When you call GpiBox with an option of DRO_FILL or DRO_OUTLINEFILL, the
  9039.  pattern will have blue vertical lines on a red background, regardless of the
  9040.  original color of the display.
  9041.  
  9042.  
  9043.  GPI Primitive 3: Text
  9044.  
  9045.  Text is the most common GPI primitive yet potentially the most complex
  9046.  because of the use of various fonts. GPI allows you to enumerate all the
  9047.  fonts available on the system and choose different fonts for the display of
  9048.  text. Many of these fonts (such as the default system font) contain
  9049.  characters of varying widths. In addition, you can alter the default spacing
  9050.  of characters to achieve such effects as justified text.
  9051.  
  9052.  The Text Output Functions
  9053.  
  9054.  GPI has four text output functions:
  9055.  
  9056.    ■  GpiCharStringAt
  9057.    ■  GpiCharString
  9058.    ■  GpiCharStringPos
  9059.    ■  GpiCharStringPosAt
  9060.  
  9061.  The GpiCharStringAt and GpiCharString Functions
  9062.  Perhaps the most common text output function is the function introduced in
  9063.  Chapter 4:
  9064.  
  9065.    GpiCharStringAt (hps, &ptl, lLength, &cString) ;
  9066.  
  9067.  The last parameter is a character array or a pointer to a character string.
  9068.  The lLength parameter is the length of this string. The POINTL structure
  9069.  indicates the starting position of the text. This is usually the baseline of
  9070.  the left side of the first character. (We'll look at an exception to this
  9071.  rule shortly.)
  9072.  
  9073.  You can also use the GpiCharString function to display text:
  9074.  
  9075.    GpiCharString (hps, lLength, &cString) ;
  9076.  
  9077.  It is the same as GpiCharStringAt, except that the text begins at the
  9078.  current position. The GpiCharStringAt function is equivalent to
  9079.  
  9080.    GpiMove (hps, &ptl) ;
  9081.    GpiCharString (hps, lLength, &cString) ;
  9082.  
  9083.  Following the GpiCharString and GpiCharStringAt calls, the current position
  9084.  is usually set to the baseline of the right side of the last character.
  9085.  (Again, there are exceptions.) Therefore, you can call GpiCharString again
  9086.  to continue a line of text.
  9087.  
  9088.  The GpiCharStringPos and GpiCharStringPosAt Functions
  9089.  Two other text output functions have some additional parameters:
  9090.  
  9091.    GpiCharStringPos (hps, &rcl, lOptions, lLength, &cString, alIncrement) ;
  9092.    GpiCharStringPosAt (hps, &ptl, &rcl, lOptions, lLength, &cString, alIncreme
  9093.  
  9094.  The GpiCharStringPos function begins the text at the current position; the
  9095.  GpiCharStringPosAt function begins the string at the POINTL structure passed
  9096.  as the second parameter. Information in the following discussion of
  9097.  GpiCharStringPos also applies to GpiCharStringPosAt.
  9098.  
  9099.  The simplest form of GpiCharStringPos results from setting the &rcl and
  9100.  alIncrement parameters to NULL and the lOptions parameter to 0:
  9101.  
  9102.    GpiCharStringPos (hps, NULL, 0L, lLength, &cString, NULL) ;
  9103.  
  9104.  In this form, the function is equivalent to GpiCharString. Nonzero lOption
  9105.  values cause some different results.
  9106.  
  9107.  You can set the lOption parameter to CHS_LEAVEPOS:
  9108.  
  9109.    GpiCharStringPos (hps, NULL, CHS_LEAVEPOS, lLength, &cString, NULL) ;
  9110.  
  9111.  On return from the function, the current position will be set at the
  9112.  beginning of the string rather than the end. That is, the GpiCharStringPos
  9113.  function leaves the current position unchanged, but GpiCharStringPosAt sets
  9114.  the current position to the POINTL structure passed to the function.
  9115.  
  9116.  If you include the &rcl parameter (a pointer to a RECTL structure), you can
  9117.  use the CHS_CLIP option:
  9118.  
  9119.    GpiCharStringPos (hps, &rcl, CHS_CLIP, lLength, &cString, NULL) ;
  9120.  
  9121.  In this case the character string will be clipped to the interior of the
  9122.  rectangle. Any part of the text string falling outside the rectangle will
  9123.  not be displayed. The &rcl parameter is also required for the CHS_OPAQUE
  9124.  option, as follows:
  9125.  
  9126.    GpiCharStringPos (hps, &rcl, CHS_OPAQUE, lLength, &cString, NULL) ;
  9127.  
  9128.  In this case the rectangle is colored with the current background color
  9129.  before the text is displayed. GPI temporarily sets the background mix to
  9130.  BM_OVERPAINT before coloring the rectangle.
  9131.  
  9132.  The fourth and final option is CHS_VECTOR. This function requires that the
  9133.  last parameter be an array of LONG integers:
  9134.  
  9135.    GpiCharStringPos (hps, NULL, CHS_VECTOR, lLength, &cString, alIncrement) ;
  9136.  
  9137.  The alIncrement array contains lLength LONG values. GPI uses this array to
  9138.  position the successive characters in the string, thereby overriding the
  9139.  default spacing. The CHS_VECTOR option is the reason for the Pos
  9140.  ("position") part of the GpiCharStringPos and GpiCharStringPosAt func tion
  9141.  names.
  9142.  
  9143.  You can use any combination of the CHS_OPAQUE, CHS_VECTOR, CHS_LEAVEPOS, and
  9144.  CHS_CLIP identifiers by combining them with the C bitwise OR operator. The
  9145.  RECTL structure passed as the second parameter is required only for
  9146.  CHS_OPAQUE or CHS_CLIP. The array of LONG increment values passed as the
  9147.  last parameter is required only when you use CHS_VECTOR.
  9148.  
  9149.  Text Color
  9150.  
  9151.  The color and mix mode affect the display of characters in the text string.
  9152.  We've already seen how the CHS_OPAQUE option in GpiCharStringPos and
  9153.  GpiCharStringPosAt functions can cause GPI to use the background color to
  9154.  color a rectangle before displaying the text.
  9155.  
  9156.  You can also use the background color and background mix with other forms of
  9157.  the text output functions. If you set the background mix to something other
  9158.  than BM_LEAVEALONE, the background color is used to color the small
  9159.  rectangular character cells that surround each character. You might want to
  9160.  do this if you are displaying text over some existing graphics and want the
  9161.  text to be more distinct. (Some GPI fonts are "outline" fonts and will not
  9162.  be affected by the background color and mix.)
  9163.  
  9164.  Font Files
  9165.  
  9166.  The subject of fonts is quite complex, yet we must attack it. As you
  9167.  discovered in Chapter 4, the default system font is proportionally spaced.
  9168.  Although we have been successful in working with this font, it is not
  9169.  appropriate for all applications. For example, a programmer's text editor or
  9170.  a communications program should probably use a fixed-pitch font, in which
  9171.  every character has the same width. We at least want to be able to switch to
  9172.  a fixed-pitch font. The ability to use boldface and italic versions of fonts
  9173.  would be nice also.
  9174.  
  9175.  GPI supports fonts in two very different formats:  "Image" fonts are stored
  9176.  as small bitmaps with 0 bits for the background of the character and 1 bits
  9177.  for the character itself. "Vector" fonts are stored as a series of straight
  9178.  lines and curves. This discussion is limited to image fonts.
  9179.  
  9180.  The OS/2 Presentation Manager includes three files that contain collections
  9181.  of image fonts in various point sizes for various output devices. These
  9182.  files, and the image fonts they contain, are shown in the following table:
  9183.  
  9184.     Font File         Font Face Name    Point Sizes
  9185.     COURIER.FON       "Courier"         8, 10, 12
  9186.     HELV.FON          "Helv"            8, 10, 12, 14, 18, 24
  9187.     TIMES.FON         "Tms Rmn"         8, 10, 12, 14, 18, 24
  9188.  
  9189.  ───────────────────────────────────────────────────────────────────────────
  9190.  NOTE:
  9191.     These three font files are stored in the C:\OS2\DLL directory on your
  9192.     hard disk. To use the fonts in these files, you must install the fonts
  9193.     from the Presentation Manager Control Panel. It is only necessary to
  9194.     install one font from each file. The Control Panel writes information to
  9195.     the OS2.INI file to load the fonts for use whenever you use the
  9196.     Presentation Manager.
  9197.  ───────────────────────────────────────────────────────────────────────────
  9198.  
  9199.  The default system font is stored in DISPLAY.DLL, the dynamic link library
  9200.  for the video display. It has a font face name of "System Proportional" and
  9201.  a point size of 12.
  9202.  
  9203.  Each font is identified by a face name and a point size. The "Courier" font
  9204.  is a fixed-pitch font similar to that produced by a typewriter. The "Helv"
  9205.  (Helvetica) and "Tms Rmn" (Times Roman) fonts are both proportional fonts.
  9206.  "Helv" is a sans serif font, which means that it does not have small lines
  9207.  finishing off the strokes of the characters. "Tms Rmn" has serifs and is
  9208.  commonly used for text in magazines and books.
  9209.  
  9210.  The point size refers to the maximum height of the characters. One point is
  9211.  approximately 1/72 inch. However, if you set page units to Low English, High
  9212.  English, or Twips, the size of the fonts will not necessarily agree with the
  9213.  GPI page units. For example, a 24-point font will not be 480 twips high. The
  9214.  size of the fonts is based on an ideal "font resolution" for the device. You
  9215.  can obtain this font resolution from DevQueryCaps using the
  9216.  CAPS_HORIZONTAL_FONT_RES and CAPS_VERTICAL_FONT_RES. As you can see from
  9217.  Figure 5-8, these values for the EGA are set to 96 pixels and 72 pixels.
  9218.  This is greater than the actual resolution of the device, to allow fonts as
  9219.  small as 8 points to be legible on the screen.
  9220.  
  9221.  The EASYFONT System
  9222.  
  9223.  Working with fonts can be difficult, but I've attempted to make it a little
  9224.  easier for you. Figure 5-19 shows two files named EASYFONT.H and EASYFONT.C
  9225.  that can greatly assist you in working with fonts in your Presentation
  9226.  Manager programs.
  9227.  
  9228.  Figure 5-19.  The EASYFONT program.
  9229.  
  9230.    The EASYFONT.H File
  9231.  
  9232.    /*---------------------------------------
  9233.       EASYFONT.H header file for EASYFONT.C
  9234.      ---------------------------------------*/
  9235.  
  9236.    BOOL EzfQueryFonts    (HPS hps) ;
  9237.    LONG EzfCreateLogFont (HPS hps, LONG lcid, USHORT idFace, USHORT idSize,
  9238.                                               USHORT fsSelection) ;
  9239.  
  9240.    #define FONTFACE_SYSTEM  0
  9241.    #define FONTFACE_COUR    1
  9242.    #define FONTFACE_HELV    2
  9243.    #define FONTFACE_TIMES   3
  9244.  
  9245.    #define FONTSIZE_8       0
  9246.    #define FONTSIZE_10      1
  9247.    #define FONTSIZE_12      2
  9248.    #define FONTSIZE_14      3
  9249.    #define FONTSIZE_18      4
  9250.    #define FONTSIZE_24      5
  9251.  
  9252.    The EASYFONT.C File
  9253.  
  9254.    /*----------------------------------------------
  9255.       EASYFONT.C -- Routines for Using Image Fonts
  9256.      ----------------------------------------------*/
  9257.  
  9258.    #define INCL_GPI
  9259.    #include <os2.h>
  9260.    #include <stdlib.h>
  9261.    #include <string.h>
  9262.    #include "easyfont.h"
  9263.  
  9264.    static SHORT sFontSize[6]   = { 80, 100, 120, 140, 180, 240 } ;
  9265.    static CHAR  *szFacename[4] = { "System Proportional",
  9266.                                    "Courier", "Helv", "Tms Rmn" } ;
  9267.    static LONG  alMatch[4][6] ;
  9268.  
  9269.    BOOL EzfQueryFonts (HPS hps)
  9270.         {
  9271.         FONTMETRICS *pfm ;
  9272.         HDC         hdc ;
  9273.         LONG        lHorzRes, lVertRes, lRequestFonts, lNumberFonts ;
  9274.         SHORT       sIndex, sFace, sSize ;
  9275.  
  9276.         hdc = GpiQueryDevice (hps) ;
  9277.         DevQueryCaps (hdc, CAPS_HORIZONTAL_FONT_RES, 1L, &lHorzRes) ;
  9278.         DevQueryCaps (hdc, CAPS_VERTICAL_FONT_RES,   1L, &lVertRes) ;
  9279.  
  9280.         for (sFace = 0 ; sFace < 4 ; sFace++)
  9281.              {
  9282.              lRequestFonts = 0 ;
  9283.              lNumberFonts = GpiQueryFonts (hps, QF_PUBLIC, szFacename[sFace],
  9284.                                            &lRequestFonts, 0L, NULL) ;
  9285.              if (lNumberFonts == 0)
  9286.                   continue ;
  9287.  
  9288.              if (lNumberFonts * sizeof (FONTMETRICS) >= 65536L)
  9289.                   return FALSE ;
  9290.  
  9291.              pfm = malloc ((SHORT) lNumberFonts * sizeof (FONTMETRICS)) ;
  9292.  
  9293.              if (pfm == NULL)
  9294.                   return FALSE ;
  9295.  
  9296.              GpiQueryFonts (hps, QF_PUBLIC, szFacename[sFace],
  9297.                             &lNumberFonts, (LONG) sizeof (FONTMETRICS), pfm) ;
  9298.  
  9299.              for (sIndex = 0 ; sIndex < (SHORT) lNumberFonts ; sIndex++)
  9300.                   if (pfm[sIndex].sXDeviceRes == (SHORT) lHorzRes &&
  9301.                       pfm[sIndex].sYDeviceRes == (SHORT) lVertRes &&
  9302.                      (pfm[sIndex].fsDefn & 1) == 0)
  9303.                        {
  9304.                        for (sSize = 0 ; sSize < 6 ; sSize++)
  9305.                             if (pfm[sIndex].sNominalPointSize == sFontSize[sSi
  9306.                                  break ;
  9307.  
  9308.                        if (sSize != 6)
  9309.                             alMatch[sFace][sSize] = pfm[sIndex].lMatch ;
  9310.                        }
  9311.  
  9312.              free (pfm) ;
  9313.              }
  9314.  
  9315.         return TRUE ;
  9316.         }
  9317.  
  9318.    LONG EzfCreateLogFont (HPS hps, LONG lcid, USHORT idFace, USHORT idSize,
  9319.                                               USHORT fsSelection)
  9320.         {
  9321.         static FATTRS fat ;
  9322.  
  9323.         if (idFace > 3 || idSize > 5 || alMatch[idFace][idSize] == 0)
  9324.              return FALSE ;
  9325.  
  9326.         fat.usRecordLength = sizeof fat ;
  9327.         fat.fsSelection    = fsSelection ;
  9328.         fat.lMatch         = alMatch[idFace][idSize] ;
  9329.  
  9330.         strcpy (fat.szFacename, szFacename[idFace]) ;
  9331.  
  9332.         return GpiCreateLogFont (hps, NULL, lcid, &fat) ;
  9333.         }
  9334.  
  9335.  
  9336.  EASYFONT.H contains declarations of the two functions in EASYFONT.C. These
  9337.  are EzfQueryFonts and EzfCreateLogFont ("create logical font"). In addition,
  9338.  EASYFONT.H contains a collection of identifiers you use as parameters to
  9339.  EzfCreateLogFont.
  9340.  
  9341.  To use EASYFONT, include the EASYFONT.H header file in your .C source code
  9342.  file:
  9343.  
  9344.    #include "easyfont.h"
  9345.  
  9346.  Then compile and link EASYFONT.C with your program.
  9347.  
  9348.  In a window procedure that uses fonts, obtain a handle to a presentation
  9349.  space during the WM_CREATE message and call EzfQueryFonts:
  9350.  
  9351.    hps = WinGetPS (hwnd) ;
  9352.    EzfQueryFonts (hps) ;
  9353.    WinReleasePS (hps) ;
  9354.  
  9355.  This performs all necessary initialization. Later on, whenever you need to
  9356.  use a non-default font, obtain a handle to a presentation space and call
  9357.  EzfCreateLogFont:
  9358.  
  9359.    EzfCreateLogFont (hps, lcid, idFace, idSize, fsSelection) ;
  9360.  
  9361.  The lcid parameter is a "local ID." It can be any number between 1 and 254.
  9362.  The idFace parameter can be any of the FONTFACE identifiers defined in
  9363.  EASYFONT.H. The idSize parameter is one of the FONTSIZE identifiers also
  9364.  defined in EASYFONT.H.
  9365.  
  9366.  Not all sizes are available for all font face names. In particular, the
  9367.  FONTFACE_SYSTEM identifier can only be used with FONTSIZE_12. The
  9368.  FONTFACE_COUR identifier can only be used with FONTSIZE_8, FONTSIZE_10, and
  9369.  FONTSIZE_12. EzfCreateLogFont returns TRUE if the font exists and FALSE
  9370.  otherwise. You should check the return value before attempting to use the
  9371.  logical font you asked for.
  9372.  
  9373.  The fsSelection parameter can be any one of the following identifiers
  9374.  defined in OS2DEF.H:
  9375.  
  9376.     Identifier                 Meaning
  9377.     FATTR_SEL_ITALIC           Italic font
  9378.     FATTR_SEL_UNDERSCORE       Underlined font
  9379.     FATTR_SEL_STRIKEOUT        Line drawn through characters
  9380.     FATTR_SEL_BOLD             Boldface font
  9381.  
  9382.  Use 0 if you want a normal font without any attributes.
  9383.  
  9384.  EzfCreateLogFont creates a logical font associated with a local ID. To use
  9385.  this font, you pass the local ID to GpiSetCharSet:
  9386.  
  9387.    GpiSetCharSet (hps, lcid) ;
  9388.  
  9389.  After this call, you can use GpiQueryFontMetrics to get the dimensions of
  9390.  the new font. Any text you draw will be displayed with the new font. Before
  9391.  releasing the presentation space, go back to the default font:
  9392.  
  9393.    GpiSetCharSet (hps, LCID_DEFAULT) ;
  9394.  
  9395.  Then delete the local ID you used:
  9396.  
  9397.    GpiDeleteSetId (hps, lcid) ;
  9398.  
  9399.  You can call EzfCreateLogFont multiple times to create different logical
  9400.  fonts, each associated with a unique local ID. You then use GpiSetCharSet to
  9401.  use any one of these fonts for text output. Be sure to set the default font
  9402.  and delete all local IDs before releasing the presentation space.
  9403.  
  9404.  The EzfQueryFonts function in EASYFONT.C first obtains the horizontal and
  9405.  vertical font resolution of the output device from DevQueryCaps. This is
  9406.  necessary because the font files contain image fonts for various output
  9407.  devices. For each of the four font faces, the function calls GpiQueryFonts
  9408.  to determine how many fonts are present and then allocates memory to store
  9409.  that number of FONTMETRICS structures. GpiQueryFonts is called again to
  9410.  obtain the FONTMETRICS structures for all the available fonts. Each font is
  9411.  checked against the device resolution and the desired point sizes. The
  9412.  function saves a field of the FONTMETRICS structure named lMatch in a static
  9413.  array. This value is used in the GpiCreateLogFont call in EzfCreateLogFont.
  9414.  
  9415.  Figure 5-20 shows a program called FONTS, which uses EASYFONT to display
  9416.  all of the fonts available for use.
  9417.  
  9418.  Figure 5-20.  The FONTS program.
  9419.  
  9420.    The FONTS File
  9421.  
  9422.    #-----------------
  9423.    # FONTS make file
  9424.    #-----------------
  9425.  
  9426.    fonts.obj : fonts.c easyfont.h
  9427.         cl -c -G2sw -W3 fonts.c
  9428.  
  9429.    easyfont.obj : easyfont.c
  9430.         cl -c -G2sw -W3 easyfont.c
  9431.  
  9432.    fonts.exe : fonts.obj easyfont.obj fonts.def
  9433.         link fonts easyfont, /align:16, NUL, os2, fonts
  9434.  
  9435.    The FONTS.C File
  9436.  
  9437.    /*----------------------------
  9438.       FONTS.C -- GPI Image Fonts
  9439.      ----------------------------*/
  9440.  
  9441.    #define INCL_WIN
  9442.    #define INCL_GPI
  9443.    #include <os2.h>
  9444.    #include <stdio.h>
  9445.    #include <stdlib.h>
  9446.    #include "easyfont.h"
  9447.  
  9448.    #define LCID_MYFONT 1L
  9449.  
  9450.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  9451.  
  9452.    int main (void)
  9453.         {
  9454.         static CHAR  szClientClass [] = "Fonts" ;
  9455.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
  9456.                                     FCF_SIZEBORDER    | FCF_MINMAX   |
  9457.                                     FCF_SHELLPOSITION | FCF_TASKLIST |
  9458.                                     FCF_VERTSCROLL    | FCF_HORZSCROLL ;
  9459.         HAB          hab ;
  9460.         HMQ          hmq ;
  9461.         HWND         hwndFrame, hwndClient ;
  9462.         QMSG         qmsg ;
  9463.  
  9464.         hab = WinInitialize (0) ;
  9465.         hmq = WinCreateMsgQueue (hab, 0) ;
  9466.  
  9467.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  9468.  
  9469.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  9470.                                         &flFrameFlags, szClientClass, NULL,
  9471.                                         0L, NULL, 0, &hwndClient) ;
  9472.  
  9473.         WinSendMsg (hwndFrame, WM_SETICON,
  9474.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  9475.                     NULL) ;
  9476.  
  9477.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  9478.              WinDispatchMsg (hab, &qmsg) ;
  9479.  
  9480.         WinDestroyWindow (hwndFrame) ;
  9481.         WinDestroyMsgQueue (hmq) ;
  9482.         WinTerminate (hab) ;
  9483.         return 0 ;
  9484.         }
  9485.  
  9486.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  9487.         {
  9488.         static CHAR   *szFace[] = { "System", "Courier",
  9489.                                     "Helv",   "Tms Rmn" } ;
  9490.         static CHAR   *szSize[] = { "8", "10", "12", "14", "18", "24" } ;
  9491.         static CHAR   *szSel[]  = { "Normal",     "Italic",  "Underscore",
  9492.                                     "Strike-out", "Bold" } ;
  9493.         static CHAR   szBuffer[80] ;
  9494.         static HWND   hwndVscroll, hwndHscroll ;
  9495.         static USHORT idFace[] = { FONTFACE_SYSTEM, FONTFACE_COUR,
  9496.                                    FONTFACE_HELV,   FONTFACE_TIMES } ;
  9497.  
  9498.         static USHORT idSize[] = { FONTSIZE_8,  FONTSIZE_10, FONTSIZE_12,
  9499.                                    FONTSIZE_14, FONTSIZE_18, FONTSIZE_24 } ;
  9500.         static USHORT afsSel[] = { 0, FATTR_SEL_ITALIC,    FATTR_SEL_UNDERSCOR
  9501.                                       FATTR_SEL_STRIKEOUT, FATTR_SEL_BOLD } ;
  9502.         static SHORT  sVscrollMax = sizeof idFace / sizeof idFace[0] - 1,
  9503.                       sHscrollMax = sizeof afsSel / sizeof afsSel[0] - 1,
  9504.                       cxClient, cyClient, sHscrollPos, sVscrollPos ;
  9505.         FONTMETRICS   fm ;
  9506.         HPS           hps;
  9507.         HWND          hwndFrame ;
  9508.         POINTL        ptl ;
  9509.         SHORT         sIndex ;
  9510.  
  9511.         switch (msg)
  9512.              {
  9513.              case WM_CREATE:
  9514.                   hps = WinGetPS (hwnd) ;
  9515.                   EzfQueryFonts (hps) ;
  9516.                   WinReleasePS (hps) ;
  9517.  
  9518.                   hwndFrame   = WinQueryWindow (hwnd, QW_PARENT, FALSE),
  9519.                   hwndVscroll = WinWindowFromID (hwndFrame, FID_VERTSCROLL) ;
  9520.                   hwndHscroll = WinWindowFromID (hwndFrame, FID_HORZSCROLL) ;
  9521.  
  9522.                   WinSendMsg (hwndVscroll, SBM_SETSCROLLBAR,
  9523.                               MPFROM2SHORT (sVscrollPos, 0),
  9524.                               MPFROM2SHORT (0, sVscrollMax)) ;
  9525.  
  9526.                   WinSendMsg (hwndHscroll, SBM_SETSCROLLBAR,
  9527.                               MPFROM2SHORT (sHscrollPos, 0),
  9528.                               MPFROM2SHORT (0, sHscrollMax)) ;
  9529.                   return 0 ;
  9530.  
  9531.              case WM_SIZE:
  9532.                   cxClient = SHORT1FROMMP (mp2) ;
  9533.                   cyClient = SHORT2FROMMP (mp2) ;
  9534.                   return 0 ;
  9535.  
  9536.              case WM_VSCROLL:
  9537.                   switch (SHORT2FROMMP (mp2))
  9538.                        {
  9539.                        case SB_LINEUP:
  9540.                        case SB_PAGEUP:
  9541.                             sVscrollPos = max (0, sVscrollPos - 1) ;
  9542.                             break ;
  9543.  
  9544.                        case SB_LINEDOWN:
  9545.                        case SB_PAGEDOWN:
  9546.                             sVscrollPos = min (sVscrollMax, sVscrollPos + 1) ;
  9547.                             break ;
  9548.  
  9549.                        case SB_SLIDERPOSITION:
  9550.                             sVscrollPos = SHORT1FROMMP (mp2) ;
  9551.                             break ;
  9552.  
  9553.                        default:
  9554.                             return 0 ;
  9555.                        }
  9556.                   WinSendMsg (hwndVscroll, SBM_SETPOS,
  9557.                               MPFROM2SHORT (sVscrollPos, 0), NULL) ;
  9558.  
  9559.                   WinInvalidateRect (hwnd, NULL, FALSE) ;
  9560.                   return 0 ;
  9561.  
  9562.              case WM_HSCROLL:
  9563.                   switch (SHORT2FROMMP (mp2))
  9564.                        {
  9565.                        case SB_LINELEFT:
  9566.                        case SB_PAGELEFT:
  9567.                             sHscrollPos = max (0, sHscrollPos - 1) ;
  9568.                             break ;
  9569.  
  9570.                        case SB_LINERIGHT:
  9571.                        case SB_PAGERIGHT:
  9572.                             sHscrollPos = min (sHscrollMax, sHscrollPos + 1) ;
  9573.                             break ;
  9574.  
  9575.                        case SB_SLIDERPOSITION:
  9576.                             sHscrollPos = SHORT1FROMMP (mp2) ;
  9577.                             break ;
  9578.  
  9579.                        default:
  9580.                             return 0 ;
  9581.                        }
  9582.                   WinSendMsg (hwndHscroll, SBM_SETPOS,
  9583.                               MPFROM2SHORT (sHscrollPos, 0), NULL) ;
  9584.  
  9585.                   WinInvalidateRect (hwnd, NULL, FALSE) ;
  9586.                   return 0 ;
  9587.  
  9588.              case WM_CHAR:
  9589.                   switch (CHARMSG(&msg)->vkey)
  9590.  
  9591.                        {
  9592.                        case VK_LEFT:
  9593.                        case VK_RIGHT:
  9594.                             return WinSendMsg (hwndHscroll, msg, mp1, mp2) ;
  9595.                        case VK_UP:
  9596.                        case VK_DOWN:
  9597.                        case VK_PAGEUP:
  9598.                        case VK_PAGEDOWN:
  9599.                             return WinSendMsg (hwndVscroll, msg, mp1, mp2) ;
  9600.                        }
  9601.                   break ;
  9602.  
  9603.              case WM_PAINT:
  9604.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  9605.                   GpiErase (hps) ;
  9606.  
  9607.                   ptl.x = 0 ;
  9608.                   ptl.y = cyClient ;
  9609.  
  9610.                   for (sIndex = 0 ; sIndex < 6 ; sIndex++)
  9611.                        if (EzfCreateLogFont (hps, LCID_MYFONT,
  9612.                                              idFace[sVscrollPos],
  9613.                                              idSize[sIndex],
  9614.                                              afsSel[sHscrollPos]))
  9615.                             {
  9616.                             GpiSetCharSet (hps, LCID_MYFONT) ;
  9617.                             GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  9618.  
  9619.                             ptl.y -= fm.lMaxBaselineExt ;
  9620.  
  9621.                             GpiCharStringAt (hps, &ptl,
  9622.                                  (LONG) sprintf (szBuffer, "%s, %s point, %s",
  9623.                                                  szFace[sVscrollPos],
  9624.                                                  szSize[sIndex],
  9625.                                                  szSel[sHscrollPos]),
  9626.                                  szBuffer) ;
  9627.  
  9628.                             GpiSetCharSet (hps, LCID_DEFAULT) ;
  9629.                             GpiDeleteSetId (hps, LCID_MYFONT) ;
  9630.                             }
  9631.  
  9632.                   WinEndPaint (hps) ;
  9633.                   return 0 ;
  9634.              }
  9635.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  9636.         }
  9637.  
  9638.    The FONTS.DEF File
  9639.  
  9640.    ;----------------------------------
  9641.    ; FONTS.DEF module definition file
  9642.    ;----------------------------------
  9643.  
  9644.    NAME           FONTS     WINDOWAPI
  9645.  
  9646.    DESCRIPTION    'Displays GPI Image Fonts (C) Charles Petzold, 1988'
  9647.    PROTMODE
  9648.    HEAPSIZE       1024
  9649.    STACKSIZE      8192
  9650.    EXPORTS        ClientWndProc
  9651.  
  9652.  
  9653.  Several programs in upcoming chapters (KEYLOOK and TYPEAWAY in Chapter 8
  9654.  and HEAD in Chapter 14) use EASYFONT to obtain a fixed-pitch Courier font
  9655.  for text output.
  9656.  
  9657.  You'll notice that the FONTS make file also compiles EASYFONT.C and links
  9658.  EASYFONT.OBJ with the program. FONTS.C includes EASYFONT.H for the function
  9659.  declarations and definitions of the FONTFACE and FONTSIZE identifiers.
  9660.  
  9661.  Each screen in FONTS shows all the available font sizes for a particular
  9662.  face name and fsSelection attribute (such as italics). You change the
  9663.  fsSelection attribute using the horizontal scroll bar and the face name
  9664.  using the vertical scroll bar. The "Tms Rmn" italic fonts are shown in
  9665.  Figure 5-21.
  9666.  
  9667.  
  9668.  GPI Primitive 4: Marker Symbols
  9669.  
  9670.  "Markers" are small symbols about the same size as a system font character.
  9671.  (To determine the size of a marker, call DevQueryCaps with the
  9672.  CAPS_MARKER_WIDTH and CAPS_MARKER_HEIGHT identifiers.) You can use markers
  9673.  as bullets or data points on a line graph.
  9674.  
  9675.  Drawing a Marker
  9676.  
  9677.  You can draw a marker by calling the following function:
  9678.  
  9679.    GpiMarker (hps, &ptl) ;
  9680.  
  9681.  GPI draws the marker with its center at the point specified in the POINTL
  9682.  structure. The current position is also set to that point.
  9683.  
  9684.  You can also draw a series of markers:
  9685.  
  9686.    GpiPolyMarker (hps, lNumber, aptl) ;
  9687.  
  9688.  The aptl parameter is an array of lNumber POINTL structures. Like
  9689.  GpiPolyLine, GpiPolyMarker is more efficient than multiple GpiMarker calls
  9690.  because the repetition occurs within the device driver. The current position
  9691.  is set to the last point.
  9692.  
  9693.  If you use markers to indicate data points on a line graph, you can use the
  9694.  same array of POINTL structures for drawing the line and drawing the
  9695.  markers. For example, suppose aptl contains sNum data points for the graph.
  9696.  This code will draw the line and the markers:
  9697.  
  9698.    GpiMove (hps, aptl) ;
  9699.    GpiPolyLine (hps, sNum - 1L, aptl + 1) ;
  9700.    GpiPolyMarker (hps, (LONG) sNum, aptl) ;
  9701.  
  9702.  Selecting a Different Marker Symbol
  9703.  
  9704.  The default marker has the appearance of a small x and has the name
  9705.  MARKSYM_CROSS. You can use the GpiSetMarker function to select a different
  9706.  marker:
  9707.  
  9708.    GpiSetMarker (hps, lSymbol) ;
  9709.  
  9710.  The lSymbol parameter can be any of the following identifiers:
  9711.  
  9712.     MARKSYM_DEFAULT             MARKSYM_EIGHTPOINTSTAR
  9713.     MARKSYM_CROSS               MARKSYM_SOLIDDIAMOND
  9714.     MARKSYM_PLUS                MARKSYM_SOLIDSQUARE
  9715.     MARKSYM_DIAMOND             MARKSYM_DOT
  9716.     MARKSYM_SQUARE              MARKSYM_SMALLCIRCLE
  9717.     MARKSYM_SIXPOINTSTAR        MARKSYM_BLANK
  9718.  
  9719.  The MARKSYM_DEFAULT identifier has the same effect as MARKSYM_CROSS.
  9720.  
  9721.  The marker is drawn using the current color and mix. The background of the
  9722.  rectangle that encompasses the marker is drawn using the current background
  9723.  color and background mix.
  9724.  
  9725.  
  9726.  GPI Primitive 5: Images
  9727.  
  9728.  I began this chapter by noting that GPI is fundamentally a vector graphics
  9729.  system. But this final GPI primitive looks like it belongs more to the realm
  9730.  of raster graphics. An "image" is a collection of bytes whose bits define a
  9731.  little picture. Each bit corresponds to a display pixel.
  9732.  
  9733.  You display an image by calling
  9734.  
  9735.    GpiImage (hps, 0L, &sizl, lLength, abData) ;
  9736.  
  9737.  The last parameter is an array of bytes that is lLength bytes long. This is
  9738.  the image data. The third parameter is a pointer to a SIZEL structure. The
  9739.  SIZEL structure is similar to the POINTL structure except that the fields
  9740.  are named cx and cy. This structure defines the width and height of the
  9741.  image in pixels.
  9742.  
  9743.  The data in abData is organized with the top row of bits first. The first
  9744.  byte contains the 8 leftmost bits of this row. The most significant bit of
  9745.  the first byte is the leftmost pixel. Each row of bits must begin with a new
  9746.  byte; if the width of the image is not a multiple of 8, the last few bits of
  9747.  the last byte of each row are not used. Thus you can calculate lLength using
  9748.  the following formula:
  9749.  
  9750.    lLength = (sizl.cx + 7) / 8 * sizl.cy ;
  9751.  
  9752.  The leftmost pixel of the top row is displayed at the current position.
  9753.  GpiImage does not change the current position. The 1 bits are displayed with
  9754.  the current foreground color and mix, and the 0 bits are displayed with the
  9755.  current background color and mix.
  9756.  
  9757.  The IMAGECAT program shown in Figure 5-22 uses the GpiImage function to
  9758.  draw a little cat in the center of the client window.
  9759.  
  9760.  Figure 5-22.  The IMAGECAT program.
  9761.  
  9762.  The IMAGECAT File
  9763.  
  9764.    #--------------------
  9765.    # IMAGECAT make file
  9766.    #--------------------
  9767.  
  9768.    imagecat.obj : imagecat.c
  9769.         cl -c -G2sw -W3 imagecat.c
  9770.  
  9771.    imagecat.exe : imagecat.obj imagecat.def
  9772.         link imagecat, /align:16, NUL, os2, imagecat
  9773.  
  9774.    The IMAGECAT.C File
  9775.  
  9776.    /*----------------------------------------
  9777.       IMAGECAT.C -- Cat drawn using GpiImage
  9778.      ----------------------------------------*/
  9779.  
  9780.    #define INCL_WIN
  9781.    #define INCL_GPI
  9782.    #include <os2.h>
  9783.    #include <stdlib.h>
  9784.  
  9785.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  9786.  
  9787.    int main (void)
  9788.         {
  9789.         static CHAR  szClientClass [] = "ImageCat" ;
  9790.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  9791.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  9792.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  9793.  
  9794.         HAB          hab ;
  9795.         HMQ          hmq ;
  9796.         HWND         hwndFrame, hwndClient ;
  9797.         QMSG         qmsg ;
  9798.  
  9799.         hab = WinInitialize (0) ;
  9800.         hmq = WinCreateMsgQueue (hab, 0) ;
  9801.  
  9802.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  9803.  
  9804.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  9805.                                         &flFrameFlags, szClientClass, NULL,
  9806.                                         0L, NULL, 0, &hwndClient) ;
  9807.  
  9808.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  9809.              WinDispatchMsg (hab, &qmsg) ;
  9810.  
  9811.         WinDestroyWindow (hwndFrame) ;
  9812.         WinDestroyMsgQueue (hmq) ;
  9813.         WinTerminate (hab) ;
  9814.         return 0 ;
  9815.         }
  9816.  
  9817.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  9818.         {
  9819.         static BYTE  abCat [] = {
  9820.                                 0x01, 0xF8, 0x1F, 0x80, 0x01, 0x04, 0x20, 0x80
  9821.                                 0x00, 0x8F, 0xF1, 0x00, 0x00, 0x48, 0x12, 0x00
  9822.                                 0x00, 0x28, 0x14, 0x00, 0x00, 0x1A, 0x58, 0x00
  9823.                                 0x00, 0x08, 0x10, 0x00, 0x00, 0xFC, 0x3F, 0x00
  9824.                                 0x00, 0x09, 0x90, 0x00, 0x00, 0xFC, 0x3F, 0x00
  9825.                                 0x00, 0x08, 0x10, 0x00, 0x00, 0x07, 0xE0, 0x00
  9826.                                 0x00, 0x08, 0x10, 0x00, 0x00, 0x08, 0x10, 0xC0
  9827.                                 0x00, 0x08, 0x10, 0x20, 0x00, 0x10, 0x08, 0x10
  9828.                                 0x00, 0x10, 0x08, 0x08, 0x00, 0x10, 0x08, 0x04
  9829.                                 0x00, 0x20, 0x04, 0x04, 0x00, 0x20, 0x04, 0x04
  9830.                                 0x00, 0x20, 0x04, 0x04, 0x00, 0x40, 0x02, 0x04
  9831.                                 0x00, 0x40, 0x02, 0x04, 0x00, 0x40, 0x02, 0x04
  9832.                                 0x00, 0xC0, 0x03, 0x04, 0x00, 0x9C, 0x39, 0x08
  9833.                                 0x00, 0xA2, 0x45, 0x08, 0x00, 0xA2, 0x45, 0x10
  9834.                                 0x00, 0xA2, 0x45, 0xE0, 0x00, 0xA2, 0x45, 0x00
  9835.                                 0x00, 0xA2, 0x45, 0x00, 0x00, 0xFF, 0xFF, 0x00
  9836.         static SHORT cxClient, cyClient ;
  9837.         HPS          hps ;
  9838.         POINTL       ptl ;
  9839.         SIZEL        sizl ;
  9840.  
  9841.         switch (msg)
  9842.              {
  9843.              case WM_SIZE:
  9844.                   cxClient = SHORT1FROMMP (mp2) ;
  9845.                   cyClient = SHORT2FROMMP (mp2) ;
  9846.                   return 0 ;
  9847.  
  9848.              case WM_PAINT:
  9849.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  9850.                   GpiErase (hps) ;
  9851.  
  9852.                   ptl.x = cxClient / 2 - 16 ;
  9853.                   ptl.y = cyClient / 2 + 16 ;
  9854.                   GpiMove (hps, &ptl) ;
  9855.  
  9856.                   sizl.cx = 32 ;
  9857.                   sizl.cy = 32 ;
  9858.                   GpiImage (hps, 0L, &sizl, (LONG) sizeof abCat, abCat) ;
  9859.  
  9860.                   WinEndPaint (hps) ;
  9861.                   return 0 ;
  9862.              }
  9863.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  9864.         }
  9865.  
  9866.  
  9867.    The IMAGECAT.DEF File
  9868.  
  9869.    ;-------------------------------------
  9870.    ; IMAGECAT.DEF module definition file
  9871.    ;-------------------------------------
  9872.  
  9873.    NAME           IMAGECAT  WINDOWAPI
  9874.  
  9875.    DESCRIPTION    'Cat Drawn Using GpiImage (C) Charles Petzold, 1988'
  9876.    PROTMODE
  9877.    HEAPSIZE       1024
  9878.    STACKSIZE      8192
  9879.    EXPORTS        ClientWndProc
  9880.  
  9881.  
  9882.  The cat is 32 pixels high and 32 pixels wide. It will look a little
  9883.  different on various output devices. Figure 5-23 shows what it looks like
  9884.  on an EGA.
  9885.  
  9886.  The GpiImage function is easy to use but extremely limited. For example, the
  9887.  function cannot alter the size of the displayed image to accommodate various
  9888.  output devices. If GpiImage were the only way to display bitmapped data in
  9889.  GPI, it would be important despite its limitations.
  9890.  
  9891.  Fortunately, GpiImage pales in comparison to the WinDrawBitMap and GpiBitBlt
  9892.  functions, which are the subject of the next chapter. Among other things,
  9893.  we'll use these functions to stretch that little cat to fill the entire
  9894.  client window.
  9895.  
  9896.  
  9897.  Chapter 6  Bitmaps and Bitblts
  9898.  ───────────────────────────────────────────────────────────────────────────
  9899.  
  9900.  
  9901.  GPI is fundamentally a vector graphics drawing system and can display
  9902.  graphics on both vector and raster output devices. For output to a vector
  9903.  device, the GPI drawing commands are translated into commands the output
  9904.  device understands. For output to a raster device, which displays an image
  9905.  composed of color dots called pixels or pels, the device driver must
  9906.  translate the GPI drawing commands into displayable pixels. If GPI were
  9907.  based on a raster model, then output to a vector device would be nearly
  9908.  impossible.
  9909.  
  9910.  But GPI is not limited to vector graphics. A Presentation Manager program
  9911.  can also draw pixels on a raster output device. Of course, drawing pixels
  9912.  one at a time can be very slow: The IBM Video Graphics Array (VGA) adapter
  9913.  running in its 640-by-480 graphics mode displays 307,200 pixels on the
  9914.  screen. A laser printer with 300-dots-per-inch resolution requires about 8
  9915.  million pixels to define an 8-1/2-by-11-inch page.
  9916.  
  9917.  Instead, a Presentation Manager program that draws pixels usually works with
  9918.  "bitmaps." A bitmap is an array of data organized into rows and columns in
  9919.  which the bits correspond to pixels on the raster output device.
  9920.  
  9921.  A bitmap can represent either a monochrome or a color image:
  9922.  
  9923.    ■  In a monochrome bitmap, each bit corresponds to one pixel. When a
  9924.       monochrome bitmap is displayed, a 0 bit usually corresponds to the
  9925.       background color and a 1 bit is the foreground color.
  9926.  
  9927.    ■  In a color bitmap, each pixel requires multiple bits to represent
  9928.       color.
  9929.  
  9930.  This chapter is generally restricted to monochrome bitmaps but will touch on
  9931.  color when necessary.
  9932.  
  9933.  Bitmaps are most suitable for small objects that must be frequently redrawn.
  9934.  For example, the mouse pointer you see on the Presentation Manager screen is
  9935.  stored as two bitmaps. Each time you move the mouse, the Presentation
  9936.  Manager must redraw the two bitmaps on the display.
  9937.  
  9938.  Bitmaps are highly device dependent. Because a bitmap represents an object
  9939.  as a series of pixels, it is usually designed for a particular device. A
  9940.  bitmap designed for the 640-by-350 resolution of the IBM Enhanced Graphics
  9941.  Adapter (EGA) will be distorted when displayed on a VGA. (You can compensate
  9942.  for this by stretching the bitmap, but this introduces other distortions.)
  9943.  
  9944.  Moreover, not all output devices are raster devices. Although every graphics
  9945.  output device attached to the Presentation Manager can handle vector
  9946.  graphics, only a raster output device can handle bitmaps. In short, don't
  9947.  expect to display a bitmap on a plotter. Even if the device driver could
  9948.  translate the bitmap into approximate plotter commands, the plotter would
  9949.  take a very long time to draw it.
  9950.  
  9951.  
  9952.  The Bit-Block Transfer
  9953.  
  9954.  You can think of the entire video display as one big bitmap. The pixels you
  9955.  see on the screen are represented by bits stored in memory on the video
  9956.  display adapter board. Any rectangular area of the video display is also a
  9957.  bitmap. Each bitmap has a size──the number of rows and columns of pixels
  9958.  it contains.
  9959.  
  9960.  Let's begin our journey into the world of bitmaps by copying a bitmap from
  9961.  one area of the video display to another. This is a job for the powerful
  9962.  GpiBitBlt function.
  9963.  
  9964.  Bitblt (pronounced "bit blit") stands for "bit-block transfer." The term was
  9965.  first used in graphics in connection with the SmallTalk system designed at
  9966.  Xerox Palo Alto Research Center (PARC). In SmallTalk, all graphics output
  9967.  operations are based around the bitblt. Among programmers, "blt" is often
  9968.  used as a verb, as in: "Blt the bitmap on the screen."
  9969.  
  9970.  The GpiBitBlt function is a pixel-mover, or (more vividly) a raster-blaster.
  9971.  As you'll see, the term "transfer" doesn't entirely do justice to the
  9972.  GpiBitBlt function. The function actually performs a bitwise operation on
  9973.  pixels and can result in some interesting effects.
  9974.  
  9975.  Simple Use of GpiBitBlt
  9976.  
  9977.  The MINMAX1 program shown in Figure 6-1 uses the GpiBitBlt function to copy
  9978.  the program's minimize-maximize menu (located in the upper-right corner of
  9979.  the frame window) to its client window.
  9980.  
  9981.  Figure 6-1.  The MINMAX1 program.
  9982.  
  9983.    The MINMAX1 File
  9984.  
  9985.    #-------------------
  9986.    # MINMAX1 make file
  9987.    #-------------------
  9988.  
  9989.    minmax1.obj : minmax1.c
  9990.         cl -c -G2sw -W3 minmax1.c
  9991.  
  9992.    minmax1.exe : minmax1.obj minmax1.def
  9993.         link minmax1, /align:16, NUL, os2, minmax1
  9994.  
  9995.    The MINMAX1.C File
  9996.  
  9997.    /*-----------------------------------------------
  9998.       MINMAX1.C -- Bitblt of Minimize-Maximize Menu
  9999.      -----------------------------------------------*/
  10000.  
  10001.    #define INCL_WIN
  10002.    #include <os2.h>
  10003.  
  10004.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  10005.  
  10006.    int main (void)
  10007.         {
  10008.         static CHAR  szClientClass [] = "MinMax1" ;
  10009.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  10010.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  10011.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  10012.         HAB          hab ;
  10013.         HMQ          hmq ;
  10014.         HWND         hwndFrame, hwndClient ;
  10015.         QMSG         qmsg ;
  10016.  
  10017.         hab = WinInitialize (0) ;
  10018.         hmq = WinCreateMsgQueue (hab, 0) ;
  10019.  
  10020.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  10021.  
  10022.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  10023.                                         &flFrameFlags, szClientClass, NULL,
  10024.                                         0L, NULL, 0, &hwndClient) ;
  10025.  
  10026.         WinSendMsg (hwndFrame, WM_SETICON,
  10027.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  10028.                     NULL) ;
  10029.  
  10030.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  10031.              WinDispatchMsg (hab, &qmsg) ;
  10032.  
  10033.         WinDestroyWindow (hwndFrame) ;
  10034.         WinDestroyMsgQueue (hmq) ;
  10035.         WinTerminate (hab) ;
  10036.         return 0 ;
  10037.         }
  10038.  
  10039.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  10040.         {
  10041.         static LONG  cxMinMax, cyMinMax ;
  10042.         static SHORT cxClient, cyClient ;
  10043.         HPS          hps ;
  10044.         POINTL       aptl[3] ;
  10045.         LONG         lRow, lCol ;
  10046.  
  10047.         switch (msg)
  10048.              {
  10049.              case WM_CREATE:
  10050.                   cxMinMax = WinQuerySysValue (HWND_DESKTOP, SV_CXMINMAXBUTTON
  10051.                   cyMinMax = WinQuerySysValue (HWND_DESKTOP, SV_CYMINMAXBUTTON
  10052.                   return 0 ;
  10053.  
  10054.              case WM_SIZE:
  10055.                   cxClient = SHORT1FROMMP (mp2) ;
  10056.                   cyClient = SHORT2FROMMP (mp2) ;
  10057.                   return 0 ;
  10058.  
  10059.              case WM_PAINT:
  10060.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  10061.  
  10062.                   GpiErase (hps) ;
  10063.  
  10064.                   for (lRow = 0 ; lRow <= cyClient / cyMinMax ; lRow++)
  10065.                        for (lCol = 0 ; lCol <= cxClient / cxMinMax ; lCol++)
  10066.                             {
  10067.                             aptl[0].x = lCol * cxMinMax ;      // target
  10068.                             aptl[0].y = lRow * cyMinMax ;      //   lower left
  10069.  
  10070.                             aptl[1].x = aptl[0].x + cxMinMax ; // target
  10071.                             aptl[1].y = aptl[0].y + cyMinMax ; //   upper righ
  10072.  
  10073.                             aptl[2].x = cxClient - cxMinMax ;  // source
  10074.                             aptl[2].y = cyClient ;             //   lower left
  10075.  
  10076.                             GpiBitBlt (hps, hps, 3L, aptl, ROP_SRCCOPY, BBO_AN
  10077.                             }
  10078.                   WinEndPaint (hps) ;
  10079.                   return 0 ;
  10080.              }
  10081.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  10082.         }
  10083.  
  10084.    The MINMAX1.DEF File
  10085.  
  10086.    ;------------------------------------
  10087.    ; MINMAX1.DEF module definition file
  10088.    ;------------------------------------
  10089.  
  10090.    NAME           MINMAX1   WINDOWAPI
  10091.  
  10092.    DESCRIPTION    'Bitblt of Min-Max Menu (C) Charles Petzold, 1988'
  10093.    PROTMODE
  10094.    HEAPSIZE       1024
  10095.    STACKSIZE      8192
  10096.    EXPORTS        ClientWndProc
  10097.  
  10098.  
  10099.  But why stop at one bitblt? In fact, MINMAX1 fills its client window with
  10100.  multiple copies of the minimize-maximize window, as shown in Figure 6-2.
  10101.  GpiBitBlt transfers pixels from a rectangular area on one presentation
  10102.  space (called the "source") to a rectangular area on another presentation
  10103.  space (the "target," or "destination"). In MINMAX1, the two presentation
  10104.  spaces are the same: the cached micro-PS associated with the program's
  10105.  client window. The source rectangle is the minimize-maximize menu; the
  10106.  destination is various rectangles on the client window.
  10107.  
  10108.  MINMAX1's minimize-maximize menu is outside the program's client window, so
  10109.  you may be surprised that GpiBitBlt can access that area of the display.
  10110.  When you're using a cached micro-PS, GPI only prohibits writing outside the
  10111.  window. GPI does indeed allow GpiBitBlt to access an area outside the
  10112.  window's limits. However, if MINMAX1 tries to repaint its client window when
  10113.  part of the minimize-maximize menu is off the screen or partially obscured,
  10114.  the image within the client window will reflect that by displaying random
  10115.  data. GpiBitBlt is reading from the screen──if the image isn't on the
  10116.  screen, the function can't read it.
  10117.  
  10118.  MINMAX1 calls the GpiBitBlt function during the WM_PAINT message based on
  10119.  information obtained during WM_CREATE and WM_SIZE. During the WM_CREATE
  10120.  message, MINMAX1 calls WinQuerySysValue to get the size of the
  10121.  minimize-maximize menu. It saves the dimensions in cxMinMax and cyMinMax.
  10122.  During the WM_SIZE message, MINMAX1 saves the size of the client window in
  10123.  cxClient and cyClient, as usual. MINMAX1 uses these variables to determine
  10124.  the number of times it calls GpiBitBlt during the WM_PAINT message.
  10125.  
  10126.  As used in MINMAX1, the GpiBitBlt function requires an array of three POINTL
  10127.  structures. This array is defined in ClientWndProc like this:
  10128.  
  10129.    POINTL aptl[3] ;
  10130.  
  10131.  During the WM_PAINT message, MINMAX1 sets the three POINTL structures with
  10132.  the coordinates (relative to the lower-left corner of the client window) of
  10133.  both the source and destination rectangles, as shown in the following table:
  10134.  
  10135.     POINTL      Meaning
  10136.     Structure
  10137.     aptl[0]     Target (or destination) of lower-left corner of bitmap
  10138.     aptl[1]     Target (or destination) of upper-right corner of bitmap
  10139.     aptl[2]     Lower-left corner of source bitmap
  10140.  
  10141.  For each copy of the minimize-maximize menu that MINMAX1 draws, aptl[2] is
  10142.  set to the lower-left corner of the source rectangle (that is, the
  10143.  lower-left corner of the minimize-maximize menu) relative to the lower-left
  10144.  corner of the client window:
  10145.  
  10146.    aptl[2].x = cxClient - cxMinMax ;
  10147.    aptl[2].y = cyClient ;
  10148.  
  10149.  For the first GpiBitBlt call during the WM_PAINT message, MINMAX1 sets
  10150.  aptl[0] to the point (0,0), which is the lower-left corner of the client
  10151.  window. The aptl[1] structure indicates the width and height of the
  10152.  destination rectangle relative to aptl[0]. This is shown in Figure 6-3.
  10153.  
  10154.  Figure 6-3.  The aptl array coordinates for the first GpiBitBlt call in
  10155.               MINMAX1.
  10156.  
  10157.              ┌──────┬───────────────────────────────────┬───┬───┐
  10158.              │ ──── │               MinMax              │  │  │
  10159.              ├──────┴───────────────────────────────────┴───┴───┤
  10160.              │                                                 │
  10161.              │                                          │       │
  10162.              │           aptl[2]=(cxClient-cxMinMax, cyClient)  │
  10163.              │                                                  │
  10164.              │                                                  │
  10165.              │                                                  │
  10166.              │                                                  │
  10167.              │                                                  │
  10168.              │                                                  │
  10169.              │                                                  │
  10170.              │                                                  │
  10171.              ├───┬───┐──aptl[1]=(cxMinMax, cyMinMax)           │
  10172.              │  │  │                                          │
  10173.              └───┴───┴──────────────────────────────────────────┘
  10174.               
  10175.               └───aptl[0]=(0, 0)
  10176.  
  10177.  
  10178.  For the subsequent GpiBitBlt calls in MINMAX1, aptl[0] and aptl[1] are the
  10179.  lower-left corner and upper-right corner of the target rectangle. This may
  10180.  be a little confusing: aptl[1] is documented as the upper-right corner of
  10181.  the destination rectangle, but in MINMAX1 it really indicates the size of
  10182.  the source bitmap. (We'll see why it's specified this way in this next
  10183.  section.)
  10184.  
  10185.  MINMAX1 passes the aptl array to GpiBitBlt:
  10186.  
  10187.    GpiBitBlt (hps, hps, 3L, aptl, ROP_SRCCOPY, BBO_AND) ;
  10188.  
  10189.  The general syntax of GpiBitBlt is as follows:
  10190.  
  10191.    GpiBitBlt (hpsDest, hpsSource, lNumPoints, aptl, lRasterOp,
  10192.               lCompressionType) ;
  10193.  
  10194.  In the case of MINMAX1, the source presentation space (hpsSource) and the
  10195.  destination presentation space (hpsDest) are the same. The lNumPoints
  10196.  parameter indicates the number of POINTL structures passed as the fourth
  10197.  parameter, in this case three. I'll discuss the last two parameters later in
  10198.  this chapter.
  10199.  
  10200.  Stretching the Bitmap
  10201.  
  10202.  The third parameter to GpiBitBlt, lNumPoints, indicates the number of POINTL
  10203.  structures in the array passed as the fourth parameter.
  10204.  
  10205.    ■  If you want the copy of the bitmap to be the same size and orientation
  10206.       as the source bitmap (as is the case in MINMAX1), set the lNumPoints
  10207.       parameter to 3L.
  10208.  
  10209.    ■  If you want to change the size of the bitmap as it is copied, you can
  10210.       use a fourth POINTL structure in the array and specify lNumPoint as 4L.
  10211.       This is illustrated in the MINMAX2 program shown in Figure 6-4.
  10212.  
  10213.  Figure 6-4.  The MINMAX2 program.
  10214.  
  10215.    The MINMAX2 File
  10216.  
  10217.    #-------------------
  10218.    # MINMAX2 make file
  10219.    #-------------------
  10220.  
  10221.    minmax2.obj : minmax2.c
  10222.         cl -c -G2sw -W3 minmax2.c
  10223.  
  10224.    minmax2.exe : minmax2.obj minmax2.def
  10225.         link minmax2, /align:16, NUL, os2, minmax2
  10226.  
  10227.    The MINMAX2.C File
  10228.  
  10229.    /*-----------------------------------------------
  10230.       MINMAX2.C -- Bitblt of Minimize-Maximize Menu
  10231.      -----------------------------------------------*/
  10232.  
  10233.    #define INCL_WIN
  10234.    #include <os2.h>
  10235.  
  10236.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  10237.  
  10238.    int main (void)
  10239.         {
  10240.         static CHAR  szClientClass [] = "MinMax2" ;
  10241.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  10242.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  10243.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  10244.         HAB          hab ;
  10245.         HMQ          hmq ;
  10246.         HWND         hwndFrame, hwndClient ;
  10247.         QMSG         qmsg ;
  10248.  
  10249.         hab = WinInitialize (0) ;
  10250.         hmq = WinCreateMsgQueue (hab, 0) ;
  10251.  
  10252.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  10253.  
  10254.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  10255.                                         &flFrameFlags, szClientClass, NULL,
  10256.                                         0L, NULL, 0, &hwndClient) ;
  10257.  
  10258.         WinSendMsg (hwndFrame, WM_SETICON,
  10259.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  10260.                     NULL) ;
  10261.  
  10262.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  10263.              WinDispatchMsg (hab, &qmsg) ;
  10264.  
  10265.         WinDestroyWindow (hwndFrame) ;
  10266.         WinDestroyMsgQueue (hmq) ;
  10267.         WinTerminate (hab) ;
  10268.         return 0 ;
  10269.         }
  10270.  
  10271.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  10272.         {
  10273.         static LONG  cxMinMax, cyMinMax ;
  10274.         static SHORT cxClient, cyClient ;
  10275.  
  10276.         HPS          hps ;
  10277.         POINTL       aptl[4] ;
  10278.  
  10279.         switch (msg)
  10280.              {
  10281.              case WM_CREATE:
  10282.                   cxMinMax = WinQuerySysValue (HWND_DESKTOP, SV_CXMINMAXBUTTON
  10283.                   cyMinMax = WinQuerySysValue (HWND_DESKTOP, SV_CYMINMAXBUTTON
  10284.                   return 0 ;
  10285.  
  10286.              case WM_SIZE:
  10287.                   cxClient = SHORT1FROMMP (mp2) ;
  10288.                   cyClient = SHORT2FROMMP (mp2) ;
  10289.                   return 0 ;
  10290.  
  10291.              case WM_PAINT:
  10292.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  10293.                   GpiErase (hps) ;
  10294.  
  10295.                   aptl[0].x = 0 ;                    // target lower left
  10296.                   aptl[0].y = 0 ;
  10297.  
  10298.                   aptl[1].x = cxClient ;             // target upper right
  10299.                   aptl[1].y = cyClient ;
  10300.  
  10301.                   aptl[2].x = cxClient - cxMinMax ;  // source lower left
  10302.                   aptl[2].y = cyClient ;
  10303.  
  10304.                   aptl[3].x = cxClient ;             // source upper right
  10305.                   aptl[3].y = cyClient + cyMinMax ;
  10306.  
  10307.                   GpiBitBlt (hps, hps, 4L, aptl, ROP_SRCCOPY, BBO_AND) ;
  10308.  
  10309.                   WinEndPaint (hps) ;
  10310.                   return 0 ;
  10311.              }
  10312.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  10313.         }
  10314.  
  10315.    The MINMAX2.DEF File
  10316.  
  10317.    ;------------------------------------
  10318.    ; MINMAX2.DEF module definition file
  10319.    ;------------------------------------
  10320.  
  10321.    NAME           MINMAX2   WINDOWAPI
  10322.  
  10323.    DESCRIPTION    'Bitblt of Min-Max Menu (C) Charles Petzold, 1988'
  10324.    PROTMODE
  10325.    HEAPSIZE       1024
  10326.    STACKSIZE      8192
  10327.    EXPORTS        ClientWndProc
  10328.  
  10329.  
  10330.  MINMAX2 calls GpiBitBlt only once during the WM_PAINT message, but it does
  10331.  so in style: The minimize-maximize menu is stretched to fill the entire
  10332.  client window, as shown in Figure 6-5.
  10333.  
  10334.  When you use an array of four POINTL structures in the GpiBitBlt function,
  10335.  they are interpreted as shown on the next page.
  10336.  
  10337.     POINTL      Meaning
  10338.     Structure
  10339.     aptl[0]     Target (or destination) of lower-left corner of bitmap
  10340.     aptl[1]     Target (or destination) of upper-right corner of bitmap
  10341.     aptl[2]     Lower-left corner of source bitmap
  10342.     aptl[3]     Upper-right corner of source bitmap
  10343.  
  10344.  In MINMAX2, the destination points are the lower-left and upper-right
  10345.  corners of the client window. The two source points are the lower-left and
  10346.  the upper-right corners of the minimize-maximize window. This is shown in
  10347.  Figure 6-6.
  10348.  
  10349.  Figure 6-6.  The aptl array coordinates for the GpiBitBlt call in MINMAX2.
  10350.  
  10351.                                   aptl[3]=(cxClient, cyClient+cyMinMax)
  10352.                                                          
  10353.       ┌──────┬───────────────────────────────────┬───┬───┐
  10354.       │ ──── │               MinMax              │  │  │
  10355.       ├──────┴───────────────────────────────────┴───┴───┤───aptl[1]=
  10356.       │                                                 │    (cxMinMax,
  10357.       │                                          │       │    cyMinMax)
  10358.       │           aptl[2]=(cxClient-cxMinMax, cyClient)  │
  10359.       │                                                  │
  10360.       │                                                  │
  10361.       │                                                  │
  10362.       │                                                  │
  10363.       │                                                  │
  10364.       │                                                  │
  10365.       │                                                  │
  10366.       │                                                  │
  10367.       └──────────────────────────────────────────────────┘
  10368.        
  10369.        └───aptl[0]=(0, 0)
  10370.  
  10371.  
  10372.  Now you can see that aptl[1] really indicates the upper-right corner of the
  10373.  target rectangle rather than the size of the bitmap.
  10374.  
  10375.  Flipping the Bitmap
  10376.  
  10377.  The aptl[0] and aptl[1] elements of the POINTL array specify the points of
  10378.  the destination rectangle that correspond to the lower-left corner and
  10379.  lower-right corner of the source bitmap. The MINMAX2 program in Figure 6-4
  10380.  sets aptl[0] and aptl[1] as shown on the next page.
  10381.  
  10382.    aptl[0].x = 0 ;
  10383.    aptl[0].y = 0 ;
  10384.  
  10385.    aptl[1].x = cxClient ;
  10386.    aptl[1].y = cyClient ;
  10387.  
  10388.  But these two points do not have to be the lower-left corner and lower-right
  10389.  corner of the destination rectangle. You can use any two opposite points for
  10390.  a variety of effects. For example, if you'd like to turn the image upside
  10391.  down, use
  10392.  
  10393.    aptl[0].x = 0 ;
  10394.    aptl[0].y = cyClient ;
  10395.  
  10396.    aptl[1].x = cxClient ;
  10397.    aptl[1].y = 0 ;
  10398.  
  10399.  The lower-left corner of the source bitmap is copied to aptl[0], which is
  10400.  now the upper-left corner of the client window. The upper-right corner of
  10401.  the source bitmap is copied to aptl[1], the lower-right corner of the client
  10402.  window.
  10403.  
  10404.  To display the minimize-maximize menu flipped around the vertical axis, use
  10405.  
  10406.    aptl[0].x = xClient ;
  10407.    aptl[0].y = 0 ;
  10408.  
  10409.    aptl[1].x = 0 ;
  10410.    aptl[1].y = yClient ;
  10411.  
  10412.  To display the minimize-maximize menu flipped on both axes, use
  10413.  
  10414.    aptl[0].x = cxClient ;
  10415.    aptl[0].y = cyClient ;
  10416.  
  10417.    aptl[1].x = 0 ;
  10418.    aptl[1].y = 0 ;
  10419.  
  10420.  When you flip a bitmap around the horizontal or vertical axis, you must
  10421.  specify four points in the aptl array, even if the bitmap is not being
  10422.  altered in size. The rule is simple: Use three points in the aptl array when
  10423.  you do not wish to change the size or orientation of the source bitmap. Use
  10424.  four points otherwise.
  10425.  
  10426.  Different Presentation Spaces
  10427.  
  10428.  The preceding examples use the same presentation space for both the source
  10429.  and the target. You can modify the MINMAX2.C source code file to use
  10430.  different presentation spaces. The destination presentation space is still
  10431.  the cached micro-PS for the client window, but the source presentation space
  10432.  can be the cached micro-PS for the minimize-maximize menu.
  10433.  
  10434.  To use this approach, you need another variable of type HPS to store the
  10435.  source presentation space handle:
  10436.  
  10437.    HPS  hpsMinMax ;
  10438.  
  10439.  During the WM_PAINT message, you can obtain hpsMinMax like this:
  10440.  
  10441.    hpsMinMax = WinGetPS (
  10442.                   WinWindowFromID (
  10443.                        WinQueryWindow (hwnd, QW_PARENT, FALSE),
  10444.                             FID_MINMAX) ;
  10445.  
  10446.  The WinQueryWindow function obtains the handle of the frame window (which is
  10447.  the parent of the client window), WinWindowFromID returns the handle to the
  10448.  minimize-maximize menu window, and WinGetPS obtains a cached micro-PS for
  10449.  this window.
  10450.  
  10451.  ───────────────────────────────────────────────────────────────────────────
  10452.  NOTE:
  10453.     At first, it seems rude to get a presentation space handle for a window
  10454.     that does not belong to your program. But the window really is part of
  10455.     your program. You created the minimize-maximize window by calling
  10456.     WinCreateStdWindow. Although the window procedure for this window is
  10457.     within the Presentation Manager, the window belongs to your program. You
  10458.     can do what you want with it.
  10459.  ───────────────────────────────────────────────────────────────────────────
  10460.  
  10461.  The aptl[0] and aptl[1] structures are set to the same values shown in
  10462.  MINMAX2.C. But the coordinates of the source bitmap are now relative to the
  10463.  lower-left corner of the source presentation space:
  10464.  
  10465.    aptl[2].x = 0 ;
  10466.    aptl[2].y = 0 ;
  10467.  
  10468.    aptl[3].x = cxMinMax ;
  10469.    aptl[3].y = cyMinMax ;
  10470.  
  10471.  The GpiBitBlt call specifies hpsMinMax as the source presentation space:
  10472.  
  10473.    GpiBitBlt (hps, hpsMinMax, 4L, aptl, ROP_SRCCOPY, BBO_AND) ;
  10474.  
  10475.  Remember to release the presentation space handle of the minimize-maximize
  10476.  menu when you're finished with it:
  10477.  
  10478.    WinReleasePS (hpsMinMax) ;
  10479.  
  10480.  The Raster Operations
  10481.  
  10482.  In MINMAX1 and MINMAX2, the source bitmap is simply copied from one area of
  10483.  the screen to another. This is the result of specifying ROP_SRCCOPY as the
  10484.  fifth parameter──the raster operation──to GpiBitBlt. ROP_SRCCOPY is only
  10485.  1 of 256 raster operations you can use in GpiBitBlt. Let's experiment with a
  10486.  few others in MINMAX2 and then investigate the raster operations more
  10487.  methodically.
  10488.  
  10489.  Try replacing ROP_SRCCOPY with ROP_NOTSRCCOPY. As the name suggests, this
  10490.  raster operation inverts the colors of the bitmap as it is copied: On the
  10491.  client window, the black area of the minimize-maximize menu becomes white,
  10492.  and white becomes black. Try ROP_ZERO: The entire client window is painted
  10493.  black. ROP_ONE causes the entire client window to be painted white.
  10494.  
  10495.  Now try replacing the GpiBitBlt call in MINMAX2 with the following two
  10496.  statements:
  10497.  
  10498.    GpiSetPattern (hps, PATSYM_HALFTONE) ;
  10499.    GpiBitBlt (hps, hps, 4L, aptl, ROP_MERGECOPY, BBO_AND) ;
  10500.  
  10501.  In this case, the black area of the minimize-maximize menu remains black
  10502.  when copied to the client window, but the white area is displayed as the
  10503.  PATSYM_HALFTONE pattern. Here's another one:
  10504.  
  10505.    GpiSetPattern (hps, PATSYM_HORIZ) ;
  10506.    GpiBitBlt (hps, hps, 4L, aptl, ROP_PATCOPY, BBO_AND) ;
  10507.  
  10508.  This simply fills the entire client window with the PATSYM_HORIZ pattern.
  10509.  Now try adding two more statements so that you call GpiSetPattern and
  10510.  GpiBitBlt twice:
  10511.  
  10512.    GpiSetPattern (hps, PATSYM_HORIZ) ;
  10513.    GpiBitBlt (hps, hps, 4L, aptl, ROP_PATCOPY, BBO_AND) ;
  10514.    GpiSetPattern (hps, PATSYM_VERT) ;
  10515.    GpiBitBlt (hps, hps, 4L, aptl, ROP_PATPAINT, BBO_AND) ;
  10516.  
  10517.  This one (shown in Figure 6-7) is strange: The black area of the
  10518.  minimize-maximize menu is now copied as white, and the white area is a
  10519.  pattern of black dots which (if you think about it) appears to be an
  10520.  "intersection" of the horizontal and vertical line patterns.
  10521.  
  10522.  Just what on earth is going on here?
  10523.  
  10524.  As I mentioned earlier, the GpiBitBlt function is not simply a bit-transfer
  10525.  function. It actually performs a bitwise operation between the following
  10526.  three bitmaps:
  10527.  
  10528.    ■  Source: The source bitmap, expanded or compressed (if necessary) to be
  10529.       the same size as the destination rectangle.
  10530.  
  10531.    ■  Destination: The destination rectangle before the GpiBitBlt call.
  10532.  
  10533.    ■  Pattern: The current pattern of the destination presentation space,
  10534.       repeated horizontally and vertically to be the same size as the
  10535.       destination rectangle.
  10536.  
  10537.  The result of this bitwise operation is copied to the destination rectangle.
  10538.  
  10539.  The raster operations are conceptually similar to the mix modes we
  10540.  encountered in Chapter 5. The mix modes govern the way in which a graphics
  10541.  object (such as a line) is combined with a destination. You'll recall that
  10542.  there were 16 foreground mix modes──all the unique results obtained when
  10543.  0s and 1s in the object are combined with 0s and 1s in the destination.
  10544.  
  10545.  The raster operations used in GpiBitBlt involve a combination of three
  10546.  objects, and this results in 256 raster operations. There are 256 ways to
  10547.  combine a source bitmap, a destination bitmap, and a pattern. Fifteen of
  10548.  these raster operations are common enough to be given names (some of them
  10549.  rather obscure) in PMGPI.H. The raster operation identifiers all begin with
  10550.  the prefix ROP. If you examine how they're defined in PMGPI.H, you'll see
  10551.  that each is defined as a number (which also seems rather obscure):
  10552.  
  10553.    #define ROP_NOTSRCCOPY 0x0033L
  10554.    #define ROP_SRCCOPY    0x00CCL
  10555.    #define ROP_PATCOPY    0x00F0L
  10556.  
  10557.  Those numbers have real meaning. They define how the source, destination,
  10558.  and pattern bitmaps are combined.
  10559.  
  10560.  Figure 6-8 shows the 15 raster operations that have names.
  10561.  
  10562.  Figure 6-8.  The 15 raster operations that have names defined in PMGPI.H.
  10563.  
  10564.  Pattern:      1   1   1   1   0   0   0   0
  10565.  Source:       1   1   0   0   1   1   0   0
  10566.  Destination:  1   0   1   0   1   0   1   0   Operation  Value   Identifier
  10567.  Result:       0   0   0   0   0   0   0   0   0          0x00    ROP_ZERO
  10568.                0   0   0   1   0   0   0   1   ~(S | D)   0x11    ROP_NOTSRCER
  10569.                0   0   1   1   0   0   1   1   ~S         0x33    ROP_NOTSRCCO
  10570.                0   1   0   0   0   1   0   0   S & ~D     0x44    ROP_SRCERASE
  10571.                0   1   0   1   0   1   0   1   ~D         0x55    ROP_DSTINVER
  10572.                0   1   0   1   1   0   1   0   P ^ D      0x5A    ROP_PATINVER
  10573.                0   1   1   0   0   1   1   0   S ^ D      0x66    ROP_SRCINVER
  10574.                1   0   0   0   1   0   0   0   S & D      0x88    ROP_SRCAND
  10575.                1   0   1   1   1   0   1   1   ~S | D     0xBB    ROP_MERGEPAI
  10576.                1   1   0   0   0   0   0   0   P & S      0xC0    ROP_MERGECOP
  10577.                1   1   0   0   1   1   0   0   S          0xCC    ROP_SRCCOPY
  10578.                1   1   1   0   1   1   1   0   S | D      0xEE    ROP_SRCPAINT
  10579.                1   1   1   1   0   0   0   0   P          0xF0    ROP_PATCOPY
  10580.                1   1   1   1   1   0   1   1   P | ~S | D 0xFB    ROP_PATPAINT
  10581.                1   1   1   1   1   1   1   1   1          0xFF    ROP_ONE
  10582.  
  10583.  
  10584.  This is an extremely important table, so let's spend a little time examining
  10585.  it.
  10586.  
  10587.  The numeric values of the ROP identifiers are listed in the second-to-last
  10588.  column. These numbers are the hexadecimal representations of the "result"
  10589.  bits shown in the first eight columns. These bits are the result of a
  10590.  bitwise operation between the pattern, source, and destination bits shown at
  10591.  the top. The Operation column uses C syntax to show how the pattern, source,
  10592.  and destination are combined.
  10593.  
  10594.  To begin understanding this table, it's easiest to assume that you're
  10595.  dealing with a monochrome system in which 0 is black and 1 is white. The
  10596.  result of the ROP_ZERO operation is all zeros regardless of the source,
  10597.  destination, and pattern, so the destination will be colored black.
  10598.  Similarly, ROP_ONE always causes the destination to be colored white.
  10599.  
  10600.  Let's take another look at these four lines of code shown earlier:
  10601.  
  10602.    GpiSetPattern (hps, PATSYM_HORIZ) ;
  10603.    GpiBitBlt (hps, hps, 4L, aptl, ROP_PATCOPY, BBO_AND) ;
  10604.    GpiSetPattern (hps, PATSYM_VERT) ;
  10605.    GpiBitBlt (hps, hps, 4L, aptl, ROP_PATPAINT, BBO_AND) ;
  10606.  
  10607.  This code was responsible for the display in Figure 6-7. As you can see
  10608.  from the table in Figure 6-8, ROP_PATCOPY causes the result bits to be
  10609.  the same as the pattern bits. The source and destination bitmaps are
  10610.  essentially ignored. In other words, ROP_PATCOPY simply copies the current
  10611.  pattern to the destination rectangle.
  10612.  
  10613.  The ROP_PATPAINT raster operation involves a more complex operation. The
  10614.  result is equal to
  10615.  
  10616.    P | ~S | D
  10617.  
  10618.  When the source bitmap is black (a 0 bit), the result is always white (a 1
  10619.  bit). Figure 6-7 verifies this. When the source is white (1), the result is
  10620.  also white if either the pattern or the destination is white. In other
  10621.  words, the result will be black only if the source is white and both the
  10622.  pattern and the destination are black. Again, Figure 6-7 verifies this.
  10623.  Black dots appeared in the white area of the source bitmap where the lines
  10624.  of the pattern that were already on the destination intersected the lines of
  10625.  the current pattern.
  10626.  
  10627.  When a raster operation does not require a source bitmap, you can set the
  10628.  second parameter of GpiBitBlt (the handle to the source presentation space)
  10629.  to NULL and the third parameter (the number of POINTL structures in the
  10630.  array) to 2L. This will speed up the drawing. The preceding example can also
  10631.  be written as
  10632.  
  10633.    GpiSetPattern (hps, PATSYM_HORIZ) ;
  10634.    GpiBitBlt (hps, NULL, 2L, aptl, ROP_PATCOPY, BBO_AND) ;
  10635.    GpiSetPattern (hps, PATSYM_VERT) ;
  10636.    GpiBitBlt (hps, hps, 4L, aptl, ROP_PATPAINT, BBO_AND) ;
  10637.  
  10638.  You don't need to use one of the predefined identifiers for the raster
  10639.  operation parameter to GpiBitBlt. You can use any number between 0 and 255.
  10640.  The hard part is determining what number to use for a particular effect.
  10641.  Here are some examples:
  10642.  
  10643.  Suppose you want to copy the white area of a source bitmap as white, but you
  10644.  want to display the PATSYM_HALFTONE pattern where the bitmap is black. You
  10645.  set up a little table similar to the one shown at the top of Figure 6-8 and
  10646.  work out the bits:
  10647.  
  10648.     Pattern:      1   1   1   1   0   0   0   0
  10649.     Source:       1   1   0   0   1   1   0   0
  10650.     Destination:  1   0   1   0   1   0   1   0   Value
  10651.     Result:       1   1   1   1   1   1   0   0   0xFC
  10652.  
  10653.  When the source is 1 (white), the result is also 1. When the source is 0
  10654.  (black), the result is the pattern. Thus the raster operation is 0xFC.
  10655.  Here's the code:
  10656.  
  10657.    GpiSetPattern (hps, PATSYM_HALFTONE) ;
  10658.    GpiBitBlt (hps, hps, 4L, aptl, 0xFC, BBO_AND) ;
  10659.  
  10660.  Simple, right?
  10661.  
  10662.  Let's try another. Where the source is white, you want the result to be
  10663.  colored with horizontal lines, and where the source is black, you want
  10664.  vertical lines. First, color the destination area using PATSYM_HORIZ and
  10665.  then set the pattern to PATSYM_VERT:
  10666.  
  10667.    GpiSetPattern (hps, PATSYM_HORIZ) ;
  10668.    GpiBitBlt (hps, NULL, 2L, aptl, ROP_PATCOPY, BBO_AND) ;
  10669.    GpiSetPattern (hps, PATSYM_VERT) ;
  10670.  
  10671.  Now all you need is a raster operation that does the following: When the
  10672.  source is 1 (white), the result is the destination; when the source is 0
  10673.  (black), the result is the pattern. Here's the table:
  10674.  
  10675.     Pattern:      1   1   1   1   0   0   0   0
  10676.     Source:       1   1   0   0   1   1   0   0
  10677.     Destination:  1   0   1   0   1   0   1   0   Value
  10678.     Result:       1   0   1   1   1   0   0   0   0xB8
  10679.  
  10680.  And here's the GpiBitBlt function that uses this raster operation:
  10681.  
  10682.    GpiBitBlt (hps, hps, 4L, aptl, 0xB8L, BBO_AND) ;
  10683.  
  10684.  Raster Operations and Color
  10685.  
  10686.  As I discussed in Chapter 5, a color display uses multiple bits for each
  10687.  pixel. For example, the EGA and VGA in high-resolution modes use 4 bits per
  10688.  pixel and can display 16 colors simultaneously. Although both the EGA and
  10689.  VGA can map these 4 bits to any one of 64 possible colors on the EGA (or
  10690.  262,144 possible colors on the VGA), the mapping is usually defined so that
  10691.  the 4 bits represent an IRGB (Intensity-Red-Green-Blue) color scheme.
  10692.  
  10693.  Like the mix mode in Chapter 5, the GpiBitBlt function performs the bitwise
  10694.  operation between each of these color bits separately. For example, if the
  10695.  destination is CLR_RED (intensity and red bits set to 1) and the source is
  10696.  CLR_PALEBLUE (blue bit set to 1), then an ROP_SRCPAINT raster operation will
  10697.  color the destination as CLR_PINK (intensity, red, and blue bits set to 1).
  10698.  
  10699.  If you are using a color display, and you have used the Presentation Manager
  10700.  Control Panel to set your window background and window text colors to
  10701.  something other than white and black, the preceding descriptions of some
  10702.  raster operations probably did not agree with your observations. Instead,
  10703.  you saw results that included the CLR_BACKGROUND and CLR_NEUTRAL colors.
  10704.  
  10705.  Here's why: When the GpiBitBlt function performs a bitwise operation on a
  10706.  source, destination, and pattern, all three bitmaps must have the same color
  10707.  format. GpiBitBlt performs the operation on the color bits separately.
  10708.  
  10709.  Patterns are stored as monochrome bitmaps. They have 1 bit per pixel. During
  10710.  GpiBitBlt the pattern must be converted to a color bitmap. That is, on the
  10711.  EGA and VGA, each bit of the pattern must be converted to 4 bits so that
  10712.  they can be combined with the source and destination. GPI does this by
  10713.  converting the 1 bits to the 4 IRGB bits that describe the current
  10714.  presentation space foreground color (the CLR_NEUTRAL color by default) and
  10715.  the 0 bits to the 4 IRGB bits for the current presentation space background
  10716.  color (CLR_BACKGROUND by default).
  10717.  
  10718.  I guarantee this will be confusing at first. By default, CLR_NEUTRAL is
  10719.  black and CLR_BACKGROUND is white. This means that 1 bits in the pattern
  10720.  become black and 0 bits become white, which is exactly the opposite of the
  10721.  interpretation of bits in a monochrome system.
  10722.  
  10723.  For example, the PATSYM_VERT pattern is mostly 0 bits except for the
  10724.  vertical lines, which are 1 bits. In a monochrome system, for example,
  10725.  PATSYM_VERT would have white lines on a black background. But when the
  10726.  pattern is converted to a color bitmap (as it must be for GPI to display it
  10727.  on a color screen), the pattern appears as CLR_NEUTRAL lines on a
  10728.  CLR_BACKGROUND background, or black on white by default.
  10729.  
  10730.  Bitblt Compression
  10731.  
  10732.  I haven't yet discussed the last parameter to GpiBitBlt. This parameter
  10733.  governs how a source bitmap is altered when it is compressed to a smaller
  10734.  destination. Three options are available: BBO_OR, BBO_AND, and BBO_IGNORE.
  10735.  
  10736.  If you considered the problem of stretching or compressing a bitmap, you
  10737.  probably assumed that GPI simply duplicates rows and columns of pixels to
  10738.  stretch a bitmap. This is correct. You may also have assumed that GPI simply
  10739.  eliminates rows and columns of pixels to compress a bitmap. But that's only
  10740.  one of the three options──the one you get when you use BBO_IGNORE, which
  10741.  is often not satisfactory.
  10742.  
  10743.  For example, suppose you have a source bitmap that has a white background
  10744.  and a 1-pixel-wide outline of a square in black. When GPI compresses the
  10745.  bitmap, the rows and columns of the bitmap containing the black lines could
  10746.  be the rows and columns that GPI eliminates. The result will be entirely
  10747.  white.
  10748.  
  10749.  When you have a bitmap with a black image on a white background, use
  10750.  BBO_AND. GPI will not eliminate whole rows and columns but instead will
  10751.  combine adjacent rows and columns of the bitmap with a bitwise AND
  10752.  operation. A result pixel will be white only if both adjacent pixels are
  10753.  also white. With a white image on a black background, use BBO_OR. Adjacent
  10754.  rows and columns are combined with a bitwise OR operation so that a result
  10755.  will be black only if adjacent pixels are black.
  10756.  
  10757.  BBO_IGNORE is for use with color bitmaps. For color bitmaps, BBO_OR and
  10758.  BBO_AND can result in the creation of colors not in the original bitmap,
  10759.  even when you're using ROP_SRCCOPY.
  10760.  
  10761.  
  10762.  Bitmap Handles and Bitmap Drawing
  10763.  
  10764.  We've been blting bitmaps around the video display but we haven't really
  10765.  gotten our hands on a bitmap, and it's not quite clear what we could do with
  10766.  one anyway.
  10767.  
  10768.  Let's temporarily abandon the GpiBitBlt function and approach bitmaps from
  10769.  another direction. We'll first try getting a handle to a bitmap and drawing
  10770.  the bitmap on the video display. After we nail down a couple of additional
  10771.  concepts, we can again bring GpiBitBlt into our collection of tools.
  10772.  
  10773.  The System Bitmaps
  10774.  
  10775.  If you've been exploring the Presentation Manager programming utilities, you
  10776.  may have discovered that ICONEDIT can create a file containing a monochrome
  10777.  bitmap. In ICONEDIT you color in the black and white pixels with a mouse and
  10778.  then save the bitmap as a file with the extension .BMP. In Chapter 12,
  10779.  you'll see how you can use that bitmap as a "resource" in a program, load it
  10780.  into memory, and display it on the screen.
  10781.  
  10782.  But you needn't jump ahead that far yet. The Presentation Manager and the
  10783.  File System program themselves use bitmaps occasionally. These are called
  10784.  "system bitmaps." These bitmaps are stored as resources in DISPLAY.DLL, the
  10785.  device driver for the video display. As I mentioned earlier, bitmaps are
  10786.  very device dependent and must often be different sizes for different video
  10787.  display drivers. Accordingly, the bitmaps are stored in the video display
  10788.  device driver.
  10789.  
  10790.  In preparation for getting your hands on a bitmap, you must define a
  10791.  variable to store a bitmap handle. A bitmap handle is of type HBITMAP:
  10792.  
  10793.    HBITMAP hbm ;
  10794.  
  10795.  An HBITMAP variable begins with hbm by convention. Now you can call
  10796.  WinGetSysBitmap:
  10797.  
  10798.    hbm = WinGetSysBitmap (HWND_DESKTOP, idSysBitmap) ;
  10799.  
  10800.  This function returns a handle to a copy of a system bitmap. The idSysBitmap
  10801.  parameter is one of the identifiers defined in PMWIN.H that begins with
  10802.  SBMP.
  10803.  
  10804.  When you've finished using the bitmap, you should delete it:
  10805.  
  10806.    GpiDeleteBitmap (hbm) ;
  10807.  
  10808.  It's okay to delete a bitmap you obtain from WinGetSysBitmap. You're not
  10809.  deleting the system bitmap itself, only the copy that was made for you.
  10810.  
  10811.  Drawing a Bitmap
  10812.  
  10813.  If you look over the identifiers beginning with SBMP, you'll find
  10814.  SBMP_MINBUTTON and SBMP_MAXBUTTON. Of course! The Presentation Manager has
  10815.  to draw the minimize-maximize menu somehow. What it uses are these system
  10816.  bitmaps.
  10817.  
  10818.  This can only mean that you're not yet done with the MINMAX series of
  10819.  programs. It's time for MINMAX3, which is shown in Figure 6-9.
  10820.  
  10821.  Figure 6-9.  The MINMAX3 program.
  10822.  
  10823.    The MINMAX3 File
  10824.  
  10825.    #-------------------
  10826.    # MINMAX3 make file
  10827.    #-------------------
  10828.  
  10829.    minmax3.obj : minmax3.c
  10830.         cl -c -G2sw -W3 minmax3.c
  10831.  
  10832.    minmax3.exe : minmax3.obj minmax3.def
  10833.         link minmax3, /align:16, NUL, os2, minmax3
  10834.  
  10835.    The MINMAX3.C File
  10836.  
  10837.    /*---------------------------------------
  10838.       MINMAX3.C -- Minimize-Maximize Bitmap
  10839.      ---------------------------------------*/
  10840.  
  10841.    #define INCL_WIN
  10842.    #include <os2.h>
  10843.  
  10844.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  10845.  
  10846.    int main (void)
  10847.         {
  10848.         static CHAR  szClientClass [] = "MinMax3" ;
  10849.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  10850.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  10851.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  10852.         HAB          hab ;
  10853.         HMQ          hmq ;
  10854.         HWND         hwndFrame, hwndClient ;
  10855.         QMSG         qmsg ;
  10856.  
  10857.         hab = WinInitialize (0) ;
  10858.         hmq = WinCreateMsgQueue (hab, 0) ;
  10859.  
  10860.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  10861.  
  10862.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  10863.                                         &flFrameFlags, szClientClass, NULL,
  10864.                                         0L, NULL, 0, &hwndClient) ;
  10865.  
  10866.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  10867.              WinDispatchMsg (hab, &qmsg) ;
  10868.  
  10869.         WinDestroyWindow (hwndFrame) ;
  10870.         WinDestroyMsgQueue (hmq) ;
  10871.         WinTerminate (hab) ;
  10872.         return 0 ;
  10873.         }
  10874.  
  10875.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  10876.         {
  10877.         static SHORT cxClient, cyClient ;
  10878.         HBITMAP      hbmMin, hbmMax ;
  10879.         HPS          hps ;
  10880.         POINTL       aptl [2] ;
  10881.  
  10882.         switch (msg)
  10883.              {
  10884.              case WM_SIZE:
  10885.                   cxClient = SHORT1FROMMP (mp2) ;
  10886.                   cyClient = SHORT2FROMMP (mp2) ;
  10887.                   return 0 ;
  10888.  
  10889.              case WM_PAINT:
  10890.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  10891.  
  10892.                   hbmMin = WinGetSysBitmap (HWND_DESKTOP, SBMP_MINBUTTON) ;
  10893.                   hbmMax = WinGetSysBitmap (HWND_DESKTOP, SBMP_MAXBUTTON) ;
  10894.  
  10895.                   aptl[0].x = 0 ;               // Target lower left
  10896.                   aptl[0].y = 0 ;
  10897.                   aptl[1].x = cxClient / 2 ;    // Target upper right
  10898.                   aptl[1].y = cyClient ;
  10899.  
  10900.                   WinDrawBitmap (hps, hbmMin, NULL, aptl,
  10901.                                  CLR_NEUTRAL, CLR_BACKGROUND, DBM_STRETCH) ;
  10902.  
  10903.                   aptl[0].x = cxClient / 2 ;    // Target left
  10904.                   aptl[1].x = cxClient ;        // Target right
  10905.  
  10906.                   WinDrawBitmap (hps, hbmMax, NULL, aptl,
  10907.                                  CLR_NEUTRAL, CLR_BACKGROUND, DBM_STRETCH) ;
  10908.  
  10909.                   GpiDeleteBitmap (hbmMin) ;
  10910.                   GpiDeleteBitmap (hbmMax) ;
  10911.  
  10912.                   WinEndPaint (hps) ;
  10913.                   return 0 ;
  10914.              }
  10915.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  10916.         }
  10917.  
  10918.    The MINMAX3.DEF File
  10919.  
  10920.    ;------------------------------------
  10921.    ; MINMAX3.DEF module definition file
  10922.    ;------------------------------------
  10923.  
  10924.    NAME           MINMAX3   WINDOWAPI
  10925.  
  10926.    DESCRIPTION    'Min-Max Bitmap (C) Charles Petzold, 1988'
  10927.    PROTMODE
  10928.    HEAPSIZE       1024
  10929.    STACKSIZE      8192
  10930.    EXPORTS        ClientWndProc
  10931.  
  10932.  
  10933.  While processing the WM_PAINT message, MINMAX3 obtains handles to the
  10934.  minimize and maximize bitmaps by calling WinGetSysBitmap twice. It stores
  10935.  the handles in hbmMin and hbmMax. The program then draws the two bitmaps on
  10936.  its client window by calling WinDrawBitmap and deletes the bitmaps using
  10937.  GpiDeleteBitmap. The MINMAX3 display is shown in Figure 6-10.
  10938.  
  10939.  The WinDrawBitmap Function
  10940.  
  10941.  As you can tell by the Win prefix, WinDrawBitmap is a high-level drawing
  10942.  function. It is certainly convenient and will be welcomed by Microsoft
  10943.  Windows programmers. (Windows had no comparable function.) But you'll soon
  10944.  see that GPI offers a better approach to drawing bitmaps.
  10945.  
  10946.  The general syntax of WinDrawBitmap is
  10947.  
  10948.    WinDrawBitmap (hps, hbm, &rclSource, &ptlDest, clrForeground,
  10949.                   clrBackground, fsOptions) ;
  10950.  
  10951.  The third parameter, &rclSource, is a pointer to a RECTL structure defining
  10952.  a rectangular area of the bitmap you want to draw. If you set this parameter
  10953.  to NULL (as MINMAX3 does), WinDrawBitmap draws the entire bitmap.
  10954.  
  10955.  The fourth parameter specifies the destination coordinates. If you do not
  10956.  include DBM_STRETCH in the options, this parameter points to a POINTL
  10957.  structure specifying the lower-left corner of the destination. If you use
  10958.  DBM_STRETCH, the parameter is an array of two POINTL structures specifying
  10959.  the lower-left corner and upper-right corner. Alternatively, you can use a
  10960.  pointer to a RECTL structure for this parameter and cast it to a PPOINTL (a
  10961.  pointer to a POINTL structure).
  10962.  
  10963.  You specify two colors for the bitmap, clrForeground and clrBackground. The
  10964.  clrForeground is used for the 1 bits of the bitmap and clrBackground is used
  10965.  for the 0 bits. (MINMAX3 uses both CLR_NEUTRAL and CLR_BACKGROUND.) If you
  10966.  have not changed the window background and window text colors in the
  10967.  Presentation Manager Control Panel, the minimize and maximize bitmaps are
  10968.  drawn as black arrows on a white background. Alternatively, you can use the
  10969.  DBM_IMAGEATTRS option in the final parameter to use the colors currently
  10970.  selected for drawing images.
  10971.  
  10972.  The fsOptions parameter can be a combination (using the C bitwise OR
  10973.  operator) of the following identifiers:
  10974.  
  10975.     Identifier                 Meaning
  10976.     DBM_NORMAL                 Draw the bitmap normally
  10977.     DBM_INVERT                 Invert the colors of the bitmap
  10978.     DBM_HALFTONE               Draw only every other bit of the bitmap
  10979.     DBM_STRETCH                Stretch the bitmap to fit the target area
  10980.     DBM_IMAGEATTRS             Use the image attributes for color
  10981.  
  10982.  The DBM_NORMAL identifier is defined as 0, so that is the default whenever
  10983.  you use a 0 as the last parameter. Both the DBM_INVERT and DBM_HALFTONE
  10984.  flags allow you to use a small subset of the 256 raster operations to draw
  10985.  the bitmap. WinDrawBitmap uses the bitmap as the source and temporarily sets
  10986.  the current pattern to PATSYM_HALFTONE (which consists of alternating 0 and
  10987.  1 bits). The last parameter to WinDrawBitmap is equivalent to the following
  10988.  raster operations:
  10989.  
  10990.     WinDrawBitmap Parameter    Raster Operation
  10991.     DBM_NORMAL                 ROP_SRCCOPY
  10992.     DBM_INVERT                 ROP_NOTSRCCOPY
  10993.     DBM_HALFTONE               0xFC (P | S)
  10994.     DBM_INVERT | DBM_HALFTONE  0x30 (P & ~S)
  10995.  
  10996.  Remember that GPI converts both the source and pattern to a color bitmap
  10997.  before performing the logical operation on each set of color bits. Thus,
  10998.  when you use ROP_NOTSRCCOPY, the 1 bits in the bitmap are colored with the
  10999.  inverse of the ROP_NEUTRAL color and the 0 bits are colored with the inverse
  11000.  of ROP_BACKGROUND.
  11001.  
  11002.  Getting Bitmap Information
  11003.  
  11004.  We managed to obtain handles to system bitmaps and draw them on the MINMAX3
  11005.  client window without knowing the size of the bitmaps. If you need this
  11006.  information, you can obtain it. First you define a variable of type
  11007.  BITMAPINFOHEADER:
  11008.  
  11009.    BITMAPINFOHEADER bmp ;
  11010.  
  11011.  The recommended prefix for structures of this type is bmp, which actually
  11012.  stands for "bitmap parameters." The BITMAPINFOHEADER structure is defined in
  11013.  PMGPI.H:
  11014.  
  11015.    typedef struct _BITMAPINFOHEADER
  11016.         {
  11017.         ULONG  cbFix ;
  11018.         USHORT cx ;
  11019.         USHORT cy ;
  11020.         USHORT cPlanes ;
  11021.         USHORT cBitCount ;
  11022.         }
  11023.         BITMAPINFOHEADER ;
  11024.  
  11025.  You first set the cbFix field as the size of the structure, which is 12
  11026.  bytes. Then you pass a pointer to this structure to
  11027.  GpiQueryBitmapParameters:
  11028.  
  11029.    GpiQueryBitmapParameters (hbm, &bmp) ;
  11030.  
  11031.  On return from the function, the cx and cy fields will contain the width and
  11032.  height of the bitmap in pixels. For a monochrome bitmap (such as all the
  11033.  system bitmaps), the cPlanes and cBitCount fields are 1. For color bitmaps,
  11034.  these two fields describe how the bitmap is organized to represent color.
  11035.  
  11036.  You also use the BITMAPINFOHEADER structure when creating a bitmap. Let's
  11037.  get to it.
  11038.  
  11039.  
  11040.  Working with Bitmaps
  11041.  
  11042.  I mentioned earlier that you can create a bitmap using the ICONEDIT program
  11043.  and store that bitmap as a resource in your program. This is certainly an
  11044.  easy approach to creating a bitmap and using it. But we'll wait for Chapter
  11045.  12 to see how that is done. Meanwhile, it is instructive to create bitmaps
  11046.  and work with them directly in a program.
  11047.  
  11048.  The Bitmap Bits
  11049.  
  11050.  What do you need to create a bitmap that represents an image? One major
  11051.  requirement is obviously the bits themselves. In a program, these bits are
  11052.  usually stored as an array of BYTE (unsigned character) values. For a
  11053.  monochrome bitmap, this array is organized as follows:
  11054.  
  11055.    ■  The array begins with the bottom row of bits.
  11056.  
  11057.    ■  The first byte in each row is the leftmost eight pixels.
  11058.  
  11059.    ■  The most significant bit in each byte is the leftmost pixel.
  11060.  
  11061.    ■  The number of bits in each row must be a multiple of the size of a
  11062.       ULONG (32 bits). If the bitmap width is not a multiple of 32, the row
  11063.       must be padded at the right.
  11064.  
  11065.    ■  A 1 bit represents the foreground color (by default, black), and a 0
  11066.       bit represents the background color (by default, white).
  11067.  
  11068.  For example, suppose you want to create a small bitmap that contains the
  11069.  word "HELLO." You want the letters to be colored with the foreground color
  11070.  (black, by default). The background will be white (by default). You can
  11071.  picture such a bitmap like this:
  11072.  
  11073.        ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
  11074.        │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │
  11075.        ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤
  11076.        │  │██│  │██│  │██│██│██│  │██│  │  │  │██│  │  │  │██│██│██│  │
  11077.        ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤
  11078.        │  │██│  │██│  │██│  │  │  │██│  │  │  │██│  │  │  │██│  │██│  │
  11079.        ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤
  11080.        │  │██│██│██│  │██│██│  │  │██│  │  │  │██│  │  │  │██│  │██│  │
  11081.        ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤
  11082.        │  │██│  │██│  │██│  │  │  │██│  │  │  │██│  │  │  │██│  │██│  │
  11083.        ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤
  11084.        │  │██│  │██│  │██│██│██│  │██│██│██│  │██│██│██│  │██│██│██│  │
  11085.        ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤
  11086.        │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │
  11087.        └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
  11088.  
  11089.  This is a "21-by-7" bitmap, with 7 rows of 21 bits each. You can represent
  11090.  the bitmap as a string of bits where the background bits are 0 and the
  11091.  foreground bits are 1:
  11092.  
  11093.    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
  11094.    0 1 0 1 0 1 1 1 0 1 0 0 0 1 0 0 0 1 1 1 0
  11095.    0 1 0 1 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 1 0
  11096.    0 1 1 1 0 1 1 0 0 1 0 0 0 1 0 0 0 1 0 1 0
  11097.    0 1 0 1 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 1 0
  11098.    0 1 0 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0
  11099.    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
  11100.  
  11101.  Group each set of 8 bits into a byte. The leftmost bits are most
  11102.  significant. Each row must be padded at the right for a multiple of 4 bytes
  11103.  per row. It doesn't matter whether you pad the rows with 0s or 1s:
  11104.  
  11105.    0x00 0x00 0x00 0x00
  11106.    0x57 0x44 0x70 0x00
  11107.    0x54 0x44 0x50 0x00
  11108.    0x76 0x44 0x50 0x00
  11109.    0x54 0x44 0x50 0x00
  11110.    0x57 0x77 0x70 0x00
  11111.    0x00 0x00 0x00 0x00
  11112.  
  11113.  Now reverse the order of the rows so that the array begins with the bottom
  11114.  row. You can define the resultant array in a program like this:
  11115.  
  11116.    static BYTE abHello [] = { 0x00, 0x00, 0x00, 0x00,
  11117.                               0x57, 0x77, 0x70, 0x00,
  11118.                               0x54, 0x44, 0x50, 0x00,
  11119.                               0x76, 0x44, 0x50, 0x00,
  11120.                               0x54, 0x44, 0x50, 0x00,
  11121.                               0x57, 0x44, 0x70, 0x00,
  11122.                               0x00, 0x00, 0x00, 0x00 } ;
  11123.  
  11124.  Bitmap Creation and Initialization
  11125.  
  11126.  To create a bitmap based on an array of bits, you use the GpiCreateBitmap
  11127.  function. This function returns a handle to the bitmap that you store in a
  11128.  variable of type HBITMAP.
  11129.  
  11130.  Before calling GpiCreateBitmap, you need two structures that are very
  11131.  similar: BITMAPINFO and BITMAPINFOHEADER. You've already seen the
  11132.  BITMAPINFOHEADER structure:
  11133.  
  11134.    typedef struct _BITMAPINFOHEADER
  11135.         {
  11136.         ULONG  cbFix ;
  11137.         USHORT cx ;
  11138.         USHORT cy ;
  11139.         USHORT cPlanes ;
  11140.         USHORT cBitCount ;
  11141.         }
  11142.         BITMAPINFOHEADER ;
  11143.  
  11144.  The prefix for a BITMAPINFOHEADER structure is bmp.
  11145.  
  11146.  The first five BITMAPINFO fields are the same as BITMAPINFOHEADER, but a
  11147.  sixth field, an array of one RGB structure, is added:
  11148.  
  11149.    typedef struct _BITMAPINFO
  11150.         {
  11151.         ULONG  cbFix ;
  11152.         USHORT cx ;
  11153.         USHORT cy ;
  11154.         USHORT cPlanes ;
  11155.         USHORT cBitCount ;
  11156.         RGB    argbColor[1] ;
  11157.         }
  11158.         BITMAPINFO ;
  11159.  
  11160.  By convention, a BITMAPINFO structure variable begins with bmi. The RGB
  11161.  structure defines a color as a combination of red, green, and blue bytes:
  11162.  
  11163.    typedef struct _RGB
  11164.         {
  11165.         BYTE bBlue ;
  11166.         BYTE bGreen ;
  11167.         BYTE bRed ;
  11168.         }
  11169.         RGB ;
  11170.  
  11171.  Each byte can range from 0 through 0xFF (255). When all 3 bytes are set to
  11172.  0, the color is black. When all 3 bytes are set to 255, the color is white.
  11173.  You need one RGB structure for each color in the bitmap. For example, if the
  11174.  bitmap has 4 color bits per pixel, you need an array of 16 RGB structures,
  11175.  one for each of the 16 possible colors. These structures indicate to GPI
  11176.  what real color corresponds to each combination of 4 bits. For a monochrome
  11177.  bitmap (which we'll be creating), you need an array of two RGB structures.
  11178.  
  11179.  In both structures, the cbFix field is set to the fixed size of the
  11180.  structure, which in both cases is 12 bytes. The cx and cy fields specify the
  11181.  size of the bitmap in bits. The cPlanes and cBitCount fields indicate how
  11182.  bits in the bitmap are organized to represent color. For a monochrome
  11183.  bitmap, these two fields are set to 1.
  11184.  
  11185.  So, to create a bitmap to contain the abHello array of bits, first define a
  11186.  BITMAPINFOHEADER structure variable and set the fields like this:
  11187.  
  11188.    BITMAPINFOHEADER bmp ;
  11189.         ....
  11190.  
  11191.    bmp.cbFix = sizeof bmp ;
  11192.    bmp.cx = 21 ;
  11193.    bmp.cy = 7 ;
  11194.    bmp.cPlanes = 1 ;
  11195.    bmp.cBitCount = 1 ;
  11196.  
  11197.  The BITMAPINFO structure is set up similarly, but it needs two RGB values
  11198.  that define how the 0 and 1 bits are interpreted. For a monochrome bitmap,
  11199.  the three fields of the first RGB structure should be set to 0, and the
  11200.  three fields of the second structure should be set to 255.
  11201.  
  11202.  And now we have a little problem. We need to define values of argbColor[0]
  11203.  and argbColor[1], but the definition of the BITMAPINFO structure is large
  11204.  enough to accommodate only one RGB structure. We need a BITMAPINFO structure
  11205.  large enough for two RGB structures. Here's one way to do it. Don't define a
  11206.  structure of type BITMAPINFO like this:
  11207.  
  11208.    BITMAPINFO bmi ;
  11209.  
  11210.  Instead, define a pointer to a BITMAPINFO structure:
  11211.  
  11212.    BITMAPINFO *pbmi ;
  11213.  
  11214.  Then use malloc to allocate enough local memory for the structure:
  11215.  
  11216.    pbmi = malloc (sizeof (BITMAPINFO) + sizeof (RGB)) ;
  11217.  
  11218.  The cbFix field is set equal to the size of the BITMAPINFO structure
  11219.  excluding the argbColor field, so you can set the fields of the structure
  11220.  like this:
  11221.  
  11222.    pbmi->cbFix = sizeof bmp ;
  11223.    pbmi->cx = 21 ;
  11224.    pbmi->cy = 7
  11225.    pbmi->cPlanes = 1 ;
  11226.    pbmi->cBitCount = 1 ;
  11227.    pbmi->argbColor[0].bBlue  = 0 ;
  11228.    pbmi->argbColor[0].bGreen = 0 ;
  11229.    pbmi->argbColor[0].bRed   = 0 ;
  11230.    pbmi->argbColor[1].bBlue  = 255 ;
  11231.    pbmi->argbColor[1].bGreen = 255 ;
  11232.    pbmi->argbColor[1].bRed   = 255 ;
  11233.  
  11234.  Now we're ready to call GpiCreateBitmap using the abHello array and these
  11235.  two structures:
  11236.  
  11237.    hbm = GpiCreateBitmap (hps, &bmp, CBM_INIT, abHello, pbmi) ;
  11238.  
  11239.  The first parameter to GpiCreateBitmap is a handle to a presentation space.
  11240.  For bitmaps to be displayed on the screen, you can use the handle returned
  11241.  from WinGetPS. If possible, GPI will use part of the video memory to store
  11242.  the bitmap. Even if the bitmap is stored in system memory, it is always
  11243.  associated with a particular device. The CBM_INIT identifier indicates that
  11244.  we want the bitmap to be initialized with the abHello data after the bitmap
  11245.  is created.
  11246.  
  11247.  After you call GpiCreateBitmap, you want to free the memory used for the
  11248.  BITMAPINFO structure:
  11249.  
  11250.    free (pbmi) ;
  11251.  
  11252.  When your program is finished using a bitmap, the bitmap should be deleted:
  11253.  
  11254.    GpiDeleteBitmap (hbm) ;
  11255.  
  11256.  You can also create a bitmap without initializing it. In this case, the
  11257.  bitmap initially contains random data. If we simply wanted to create an
  11258.  uninitialized 21-by-7 bitmap, the GpiCreateBitmap function would be
  11259.  
  11260.    hbm = GpiCreateBitmap (hps, &bmp, 0L, NULL, NULL) ;
  11261.  
  11262.  Notice that only the BITMAPINFOHEADER structure is required for this
  11263.  variation of the GpiCreateBitmap call. You don't need to tell GPI how to
  11264.  interpret color information when creating the uninitialized bitmap.
  11265.  
  11266.  The Bit Cat
  11267.  
  11268.  Now that we have some of the concepts down, let's look at a program that
  11269.  creates and displays a bitmap. Rather than the simple "HELLO" bitmap
  11270.  described previously, this program uses a more interesting bitmap. Remember
  11271.  the cat we displayed in Chapter 5 using GpiImage? We'll now display that
  11272.  cat as a bitmap. BITCAT1 is shown in Figure 6-11.
  11273.  
  11274.  Figure 6-11.  The BITCAT1 program.
  11275.  
  11276.    The BITCAT1 File
  11277.  
  11278.    #-------------------
  11279.    # BITCAT1 make file
  11280.    #-------------------
  11281.  
  11282.    bitcat1.obj : bitcat1.c bitcat.h
  11283.         cl -c -G2sw -W3 bitcat1.c
  11284.  
  11285.    bitcat1.exe : bitcat1.obj bitcat1.def
  11286.         link bitcat1, /align:16, NUL, os2, bitcat1
  11287.  
  11288.    The BITCAT1.C File
  11289.  
  11290.    /*------------------------------------------
  11291.       BITCAT1.C -- Bitmap Creation and Display
  11292.      ------------------------------------------*/
  11293.  
  11294.    #define INCL_WIN
  11295.    #define INCL_GPI
  11296.    #include <os2.h>
  11297.    #include <stdlib.h>
  11298.    #include "bitcat.h"
  11299.  
  11300.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  11301.  
  11302.    int main (void)
  11303.         {
  11304.         static CHAR  szClientClass [] = "BitCat1" ;
  11305.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  11306.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  11307.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  11308.         HAB          hab ;
  11309.         HMQ          hmq ;
  11310.         HWND         hwndFrame, hwndClient ;
  11311.         QMSG         qmsg ;
  11312.  
  11313.         hab = WinInitialize (0) ;
  11314.         hmq = WinCreateMsgQueue (hab, 0) ;
  11315.  
  11316.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  11317.  
  11318.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  11319.                                         &flFrameFlags, szClientClass, NULL,
  11320.                                         0L, NULL, 0, &hwndClient) ;
  11321.  
  11322.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  11323.              WinDispatchMsg (hab, &qmsg) ;
  11324.  
  11325.         WinDestroyWindow (hwndFrame) ;
  11326.         WinDestroyMsgQueue (hmq) ;
  11327.         WinTerminate (hab) ;
  11328.         return 0 ;
  11329.         }
  11330.  
  11331.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  11332.         {
  11333.         static HBITMAP   hbm ;
  11334.         BITMAPINFO       *pbmi ;
  11335.         BITMAPINFOHEADER bmp ;
  11336.         HPS              hps ;
  11337.         RECTL            rcl ;
  11338.  
  11339.         switch (msg)
  11340.              {
  11341.              case WM_CREATE:
  11342.  
  11343.                             /*-----------------------------------
  11344.                                Create 32-by-32 monochrome bitmap
  11345.                               -----------------------------------*/
  11346.  
  11347.                   bmp.cbFix     = sizeof bmp ;
  11348.                   bmp.cx        = 32 ;
  11349.                   bmp.cy        = 32 ;
  11350.                   bmp.cPlanes   = 1 ;
  11351.                   bmp.cBitCount = 1 ;
  11352.  
  11353.                   pbmi = malloc (sizeof (BITMAPINFO) + sizeof (RGB)) ;
  11354.  
  11355.                   pbmi->cbFix     = sizeof bmp ;
  11356.                   pbmi->cx        = 32 ;
  11357.                   pbmi->cy        = 32 ;
  11358.                   pbmi->cPlanes   = 1 ;
  11359.                   pbmi->cBitCount = 1;
  11360.  
  11361.                   pbmi->argbColor[0].bBlue  = 0 ;
  11362.                   pbmi->argbColor[0].bGreen = 0 ;
  11363.                   pbmi->argbColor[0].bRed   = 0 ;
  11364.                   pbmi->argbColor[1].bBlue  = 0xFF ;
  11365.                   pbmi->argbColor[1].bGreen = 0xFF ;
  11366.                   pbmi->argbColor[1].bRed   = 0xFF ;
  11367.  
  11368.                   hps = WinGetPS (hwnd) ;
  11369.                   hbm = GpiCreateBitmap (hps, &bmp, CBM_INIT, abBitCat, pbmi)
  11370.  
  11371.                   WinReleasePS (hps) ;
  11372.                   free (pbmi) ;
  11373.                   return 0 ;
  11374.  
  11375.              case WM_PAINT:
  11376.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  11377.  
  11378.                   WinQueryWindowRect (hwnd, &rcl) ;
  11379.  
  11380.                   WinDrawBitmap (hps, hbm, NULL, (PPOINTL) &rcl,
  11381.                                  CLR_NEUTRAL, CLR_BACKGROUND, DBM_STRETCH) ;
  11382.  
  11383.                   WinDrawBitmap (hps, hbm, NULL, (PPOINTL) &rcl,
  11384.                                  CLR_NEUTRAL, CLR_BACKGROUND, DBM_NORMAL) ;
  11385.  
  11386.                   WinEndPaint (hps) ;
  11387.                   return 0 ;
  11388.  
  11389.              case WM_DESTROY:
  11390.                   GpiDeleteBitmap (hbm) ;
  11391.                   return 0 ;
  11392.              }
  11393.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  11394.         }
  11395.  
  11396.    The BITCAT.H File
  11397.  
  11398.    /*----------------------
  11399.       BITCAT.H header file
  11400.      ----------------------*/
  11401.  
  11402.    static BYTE abBitCat[] = {0x00, 0xFF, 0xFF, 0x00, 0x00, 0xA2, 0x45, 0x00,
  11403.                              0x00, 0xA2, 0x45, 0x00, 0x00, 0xA2, 0x45, 0xE0,
  11404.                              0x00, 0xA2, 0x45, 0x10, 0x00, 0xA2, 0x45, 0x08,
  11405.                              0x00, 0x9C, 0x39, 0x08, 0x00, 0xC0, 0x03, 0x04,
  11406.  
  11407.                              0x00, 0x40, 0x02, 0x04, 0x00, 0x40, 0x02, 0x04,
  11408.                              0x00, 0x40, 0x02, 0x04, 0x00, 0x20, 0x04, 0x04,
  11409.                              0x00, 0x20, 0x04, 0x04, 0x00, 0x20, 0x04, 0x04,
  11410.                              0x00, 0x10, 0x08, 0x04, 0x00, 0x10, 0x08, 0x08,
  11411.  
  11412.                              0x00, 0x10, 0x08, 0x10, 0x00, 0x08, 0x10, 0x20,
  11413.                              0x00, 0x08, 0x10, 0xC0, 0x00, 0x08, 0x10, 0x00,
  11414.                              0x00, 0x07, 0xE0, 0x00, 0x00, 0x08, 0x10, 0x00,
  11415.                              0x00, 0xFC, 0x3F, 0x00, 0x00, 0x09, 0x90, 0x00,
  11416.  
  11417.                              0x00, 0xFC, 0x3F, 0x00, 0x00, 0x08, 0x10, 0x00,
  11418.                              0x00, 0x1A, 0x58, 0x00, 0x00, 0x28, 0x14, 0x00,
  11419.                              0x00, 0x48, 0x12, 0x00, 0x00, 0x8F, 0xF1, 0x00,
  11420.                              0x01, 0x04, 0x20, 0x80, 0x01, 0xF8, 0x1F, 0x80 }
  11421.  
  11422.    The BITCAT1.DEF File
  11423.  
  11424.    ;------------------------------------
  11425.    ; BITCAT1.DEF module definition file
  11426.    ;------------------------------------
  11427.  
  11428.    NAME           BITCAT1   WINDOWAPI
  11429.  
  11430.    DESCRIPTION    'Bitmap Creation and Display (C) Charles Petzold, 1988'
  11431.    PROTMODE
  11432.    HEAPSIZE       1024
  11433.    STACKSIZE      8192
  11434.    EXPORTS        ClientWndProc
  11435.  
  11436.  
  11437.  The abBitCat array in BITCAT.H contains the bytes that define the picture of
  11438.  the cat. Note that the rows are in reverse order from the rows used in
  11439.  GpiImage. (GpiImage requires the top row first.) The BITCAT1 program creates
  11440.  the 32-by-32 bitmap during the WM_CREATE message and deletes it during the
  11441.  WM_DESTROY message.
  11442.  
  11443.  During the WM_PAINT message, BITCAT1 draws the bitmap twice using the
  11444.  WinDrawBitmap function. The first call draws the bitmap to fill the entire
  11445.  client window. The second call draws the bitmap in the lower-left corner of
  11446.  the client window in its actual pixel size. This is shown in Figure 6-12.
  11447.  
  11448.  Bitmaps and Bitblts
  11449.  
  11450.  I mentioned earlier that the WinDrawBitmap function is convenient but that
  11451.  GPI has a better way to draw a bitmap using our old friend, the GpiBitBlt
  11452.  function. This will be demonstrated shortly in the BITCAT2 program.
  11453.  
  11454.  You may resist this new method at first because BITCAT2.C is longer than
  11455.  BITCAT1.C and somewhat more complex. However, this method unleashes all the
  11456.  power available in the GpiBitBlt function when drawing a bitmap. This is
  11457.  where bitmaps and bitblts come together as two related tools.
  11458.  
  11459.  You'll recall that GpiBitBlt transfers a bitmap from one presentation space
  11460.  to another, possibly combining it with the current pattern set in the
  11461.  destination presentation space. There doesn't seem to be any place in the
  11462.  function for a handle to a bitmap. To use the GpiBitBlt function to draw a
  11463.  bitmap, we must first make the bitmap part of a presentation space. This
  11464.  requires a concept that is very important for working with bitmaps: the
  11465.  memory device context.
  11466.  
  11467.  The Memory Device Context
  11468.  
  11469.  In Chapter 5 we worked briefly with the device context for the video
  11470.  display. "Device context" is a term used to describe the combination of an
  11471.  output device and its device driver. A presentation space is associated with
  11472.  a device context. When you call GPI drawing functions for a particular
  11473.  presentation space, GPI draws the objects on the device context associated
  11474.  with the presentation space.
  11475.  
  11476.  We're going to create a device context that exists only in memory. This
  11477.  device context is not a real output device. It is called the memory device
  11478.  context. To create this device context, you call DevOpenDC with a second
  11479.  parameter set to the identifier OD_MEMORY and the other parameters as shown
  11480.  here:
  11481.  
  11482.    hdcMemory = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL) ;
  11483.  
  11484.  You then create a presentation space associated with this memory device
  11485.  context by calling GpiCreatePS:
  11486.  
  11487.    hpsMemory = GpiCreatePS (hab, hdcMemory, &sizl,
  11488.                             PU_PELS    | GPIF_DEFAULT |
  11489.                             GPIT_MICRO | GPIA_ASSOC) ;
  11490.  
  11491.  This presentation space is associated with the memory device context. The
  11492.  third parameter is a pointer to a structure of type SIZEL with two fields
  11493.  named cx and cy. Before calling GpiCreatePS, you set these two fields to 0.
  11494.  
  11495.  Here comes the crucial step: You call GpiSetBitmap to set a bitmap in this
  11496.  presentation space:
  11497.  
  11498.    GpiSetBitmap (hpsMemory, hbm) ;
  11499.  
  11500.  This function seems a little strange at first. Near the beginning of this
  11501.  chapter I said that you could imagine the entire video display as one big
  11502.  bitmap. The video adapter board contains a large block of memory that
  11503.  contains (in one form or another) the digital representation of the image on
  11504.  the screen.
  11505.  
  11506.  When you call GpiSetBitmap, the bitmap becomes the display surface of the
  11507.  memory device context associated with the presentation space. You can then
  11508.  use this presentation space as a source (or destination) with functions such
  11509.  as GpiBitBlt. Moreover, anything you draw on this presentation space is
  11510.  actually drawn on the bitmap.
  11511.  
  11512.  When you are finished using the presentation space, the memory device
  11513.  context, and the bitmap, you destroy them in this order:
  11514.  
  11515.    GpiDestroyPS (hpsMemory) ;
  11516.    DevCloseDC (hdcMemory) ;
  11517.    GpiDeleteBitmap (hbm) ;
  11518.  
  11519.  Now let's look at BITCAT2, which uses this approach. The program is shown in
  11520.  Figure 6-13.
  11521.  
  11522.  Figure 6-13.  The BITCAT2 program.
  11523.  
  11524.    The BITCAT2 File
  11525.  
  11526.    #-------------------
  11527.    # BITCAT2 make file
  11528.    #-------------------
  11529.  
  11530.    bitcat2.obj : bitcat2.c bitcat.h
  11531.         cl -c -G2sw -W3 bitcat2.c
  11532.  
  11533.    bitcat2.exe : bitcat2.obj bitcat2.def
  11534.         link bitcat2, /align:16, NUL, os2, bitcat2
  11535.  
  11536.    The BITCAT2.C File
  11537.  
  11538.    /*------------------------------------------
  11539.       BITCAT2.C -- Bitmap Creation and Display
  11540.      ------------------------------------------*/
  11541.  
  11542.    #define INCL_WIN
  11543.    #define INCL_GPI
  11544.    #include <os2.h>
  11545.    #include <stdlib.h>
  11546.    #include "bitcat.h"
  11547.  
  11548.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  11549.  
  11550.    HAB  hab ;
  11551.  
  11552.    int main (void)
  11553.         {
  11554.         static CHAR  szClientClass [] = "BitCat2" ;
  11555.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
  11556.                                     FCF_SIZEBORDER    | FCF_MINMAX   |
  11557.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  11558.         HMQ          hmq ;
  11559.         HWND         hwndFrame, hwndClient ;
  11560.  
  11561.         QMSG         qmsg ;
  11562.  
  11563.         hab = WinInitialize (0) ;
  11564.         hmq = WinCreateMsgQueue (hab, 0) ;
  11565.  
  11566.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  11567.  
  11568.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  11569.                                         &flFrameFlags, szClientClass, NULL,
  11570.                                         0L, NULL, 0, &hwndClient) ;
  11571.  
  11572.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  11573.              WinDispatchMsg (hab, &qmsg) ;
  11574.  
  11575.         WinDestroyWindow (hwndFrame) ;
  11576.         WinDestroyMsgQueue (hmq) ;
  11577.         WinTerminate (hab) ;
  11578.         return 0 ;
  11579.         }
  11580.  
  11581.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  11582.         {
  11583.         static HBITMAP   hbm ;
  11584.         static HDC       hdcMemory ;
  11585.         static HPS       hpsMemory ;
  11586.         static SHORT     cxClient, cyClient ;
  11587.         BITMAPINFO       *pbmi ;
  11588.         BITMAPINFOHEADER bmp ;
  11589.         HPS              hps ;
  11590.         POINTL           aptl [4] ;
  11591.         SIZEL            sizl ;
  11592.  
  11593.         switch (msg)
  11594.              {
  11595.              case WM_CREATE:
  11596.  
  11597.                             /*------------------------------------------------
  11598.                                Open memory DC and create PS associated with it
  11599.                               ------------------------------------------------
  11600.  
  11601.                   hdcMemory = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL)
  11602.  
  11603.                   sizl.cx = 0 ;
  11604.                   sizl.cy = 0 ;
  11605.  
  11606.                   hpsMemory = GpiCreatePS (hab, hdcMemory, &sizl,
  11607.                                            PU_PELS    | GPIF_DEFAULT |
  11608.                                            GPIT_MICRO | GPIA_ASSOC) ;
  11609.  
  11610.                             /*------------------------
  11611.                                Create 32 by 32 bitmap
  11612.                               ------------------------*/
  11613.  
  11614.                   bmp.cbFix     = sizeof bmp ;
  11615.                   bmp.cx        = 32 ;
  11616.                   bmp.cy        = 32 ;
  11617.                   bmp.cPlanes   = 1 ;
  11618.                   bmp.cBitCount = 1 ;
  11619.  
  11620.                   hbm = GpiCreateBitmap (hpsMemory, &bmp, 0L, NULL, NULL) ;
  11621.  
  11622.                             /*------------------------------
  11623.                                Select bitmap into memory PS
  11624.                               ------------------------------*/
  11625.  
  11626.                   GpiSetBitmap (hpsMemory, hbm) ;
  11627.  
  11628.                             /*-------------------------------------
  11629.                                Set bitmap bits from abBitCat array
  11630.                               -------------------------------------*/
  11631.  
  11632.                   pbmi = malloc (sizeof (BITMAPINFO) + sizeof (RGB)) ;
  11633.  
  11634.                   pbmi->cbFix     = sizeof bmp ;
  11635.                   pbmi->cx        = 32 ;
  11636.                   pbmi->cy        = 32 ;
  11637.                   pbmi->cPlanes   = 1 ;
  11638.                   pbmi->cBitCount = 1 ;
  11639.  
  11640.                   pbmi->argbColor[0].bBlue  = 0 ;
  11641.                   pbmi->argbColor[0].bGreen = 0 ;
  11642.                   pbmi->argbColor[0].bRed   = 0 ;
  11643.                   pbmi->argbColor[1].bBlue  = 0xFF ;
  11644.                   pbmi->argbColor[1].bGreen = 0xFF ;
  11645.                   pbmi->argbColor[1].bRed   = 0xFF ;
  11646.  
  11647.                   GpiSetBitmapBits (hpsMemory, 0L, 32L, abBitCat, pbmi) ;
  11648.  
  11649.                   free (pbmi) ;
  11650.                   return 0 ;
  11651.  
  11652.              case WM_SIZE:
  11653.                   cxClient = SHORT1FROMMP (mp2) ;
  11654.                   cyClient = SHORT2FROMMP (mp2) ;
  11655.                   return 0 ;
  11656.  
  11657.              case WM_PAINT:
  11658.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  11659.  
  11660.                   aptl[0].x = 0 ;                    // target lower left
  11661.                   aptl[0].y = 0 ;
  11662.  
  11663.                   aptl[1].x = cxClient ;             // target upper right
  11664.                   aptl[1].y = cyClient ;
  11665.  
  11666.                   aptl[2].x = 0 ;                    // source lower left
  11667.                   aptl[2].y = 0 ;
  11668.  
  11669.                   aptl[3].x = 32 ;                   // source upper right
  11670.                   aptl[3].y = 32 ;
  11671.  
  11672.                   GpiBitBlt (hps, hpsMemory, 4L, aptl, ROP_SRCCOPY, BBO_AND) ;
  11673.  
  11674.                   aptl[1] = aptl[3] ;                // target upper right
  11675.  
  11676.                   GpiBitBlt (hps, hpsMemory, 3L, aptl, ROP_SRCCOPY, BBO_AND) ;
  11677.  
  11678.                   WinEndPaint (hps) ;
  11679.                   return 0 ;
  11680.  
  11681.              case WM_DESTROY:
  11682.                   GpiDestroyPS (hpsMemory) ;
  11683.                   DevCloseDC (hdcMemory) ;
  11684.                   GpiDeleteBitmap (hbm) ;
  11685.                   return 0 ;
  11686.              }
  11687.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  11688.         }
  11689.  
  11690.    The BITCAT2.DEF File
  11691.  
  11692.    ;------------------------------------
  11693.    ; BITCAT2.DEF module definition file
  11694.    ;------------------------------------
  11695.  
  11696.    NAME           BITCAT2   WINDOWAPI
  11697.  
  11698.    DESCRIPTION    'Bitmap Creation and Display (C) Charles Petzold, 1988'
  11699.    PROTMODE
  11700.    HEAPSIZE       1024
  11701.    STACKSIZE      8192
  11702.    EXPORTS        ClientWndProc
  11703.  
  11704.  
  11705.  The BITCAT2 program also requires the BITCAT.H header file from Figure
  11706.  6-11. As you can see from Figure 6-14, the program shows the same
  11707.  output as BITCAT1:
  11708.  
  11709.  During the WM_CREATE message, BITCAT2 opens a memory device context and
  11710.  creates a presentation space associated with that device context. BITCAT2
  11711.  creates an initialized 32-by-32 bitmap exactly like BITCAT1. But then it
  11712.  calls GpiSetBitmap to set the bitmap in the presentation space.
  11713.  
  11714.  During the WM_PAINT message, BITCAT2 uses the GpiBitBlt function to draw the
  11715.  bitmap on the display. The source presentation space is simply the
  11716.  presentation space associated with the memory device context. Although
  11717.  BITCAT2 uses ROP_SRCCOPY, it could also select a nondefault pattern in the
  11718.  destination presentation space and use GpiBitBlt with all 256 raster
  11719.  operations.
  11720.  
  11721.  For drawing bitmaps, GPI also provides a function similar to GpiBitBlt──
  11722.  called GpiWCBitBlt. (The WC stands for "word coordinates.") The second
  11723.  parameter is a handle to a bitmap rather than a handle to a source
  11724.  presentation space.
  11725.  
  11726.  Drawing on the Memory Device Context
  11727.  
  11728.  I said earlier that the bitmap is the display surface of the memory device
  11729.  context. Thus, when you use normal GPI functions to draw on the presentation
  11730.  space associated with this memory device context, you're actually drawing on
  11731.  the bitmap. This is one reason why you can create a bitmap without
  11732.  initializing it. You can create an image on the bitmap by simply drawing on
  11733.  the presentation space.
  11734.  
  11735.  This is shown in the HELLOBIT program in Figure 6-15.
  11736.  
  11737.  Figure 6-15.  The HELLOBIT program.
  11738.  
  11739.    The HELLOBIT File
  11740.  
  11741.    #--------------------
  11742.    # HELLOBIT make file
  11743.    #--------------------
  11744.  
  11745.    hellobit.obj : hellobit.c
  11746.         cl -c -G2sw -W3 hellobit.c
  11747.  
  11748.    hellobit.exe : hellobit.obj hellobit.def
  11749.         link hellobit, /align:16, NUL, os2, hellobit
  11750.  
  11751.    The HELLOBIT.C File
  11752.  
  11753.    /*-------------------------------------
  11754.       HELLOBIT.C -- "Hello, world" Bitmap
  11755.      -------------------------------------*/
  11756.  
  11757.    #define INCL_WIN
  11758.    #define INCL_GPI
  11759.    #include <os2.h>
  11760.  
  11761.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  11762.  
  11763.    HAB  hab ;
  11764.  
  11765.    int main (void)
  11766.         {
  11767.         static CHAR  szClientClass [] = "HelloBit" ;
  11768.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  11769.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  11770.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  11771.         HMQ          hmq ;
  11772.         HWND         hwndFrame, hwndClient ;
  11773.         QMSG         qmsg ;
  11774.  
  11775.         hab = WinInitialize (0) ;
  11776.         hmq = WinCreateMsgQueue (hab, 0) ;
  11777.  
  11778.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  11779.  
  11780.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  11781.                                         &flFrameFlags, szClientClass, NULL,
  11782.                                         0L, NULL, 0, &hwndClient) ;
  11783.  
  11784.         WinSendMsg (hwndFrame, WM_SETICON,
  11785.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  11786.                     NULL) ;
  11787.  
  11788.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  11789.              WinDispatchMsg (hab, &qmsg) ;
  11790.  
  11791.         WinDestroyWindow (hwndFrame) ;
  11792.         WinDestroyMsgQueue (hmq) ;
  11793.         WinTerminate (hab) ;
  11794.         return 0 ;
  11795.         }
  11796.  
  11797.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  11798.         {
  11799.         static CHAR      szHello [] = " Hello, world " ;
  11800.         static HBITMAP   hbm ;
  11801.         static HDC       hdcMemory ;
  11802.         static HPS       hpsMemory ;
  11803.         static SHORT     cxClient, cyClient, cxString, cyString ;
  11804.         BITMAPINFOHEADER bmp ;
  11805.         HPS              hps ;
  11806.         POINTL           aptl [4], ptl ;
  11807.  
  11808.         SHORT            x, y ;
  11809.         SIZEL            sizl ;
  11810.  
  11811.         switch (msg)
  11812.              {
  11813.              case WM_CREATE:
  11814.  
  11815.                             /*------------------------------------------------
  11816.                                Open memory DC and create PS associated with it
  11817.                               ------------------------------------------------
  11818.  
  11819.                   hdcMemory = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL)
  11820.  
  11821.                   sizl.cx = 0 ;
  11822.                   sizl.cy = 0 ;
  11823.                   hpsMemory = GpiCreatePS (hab, hdcMemory, &sizl,
  11824.                                            PU_PELS    | GPIF_DEFAULT |
  11825.                                            GPIT_MICRO | GPIA_ASSOC) ;
  11826.  
  11827.                             /*-------------------------------------
  11828.                                Determine dimensions of text string
  11829.                               -------------------------------------*/
  11830.  
  11831.                   GpiQueryTextBox (hpsMemory, sizeof szHello - 1L,
  11832.                                    szHello, 4L, aptl) ;
  11833.  
  11834.                   cxString = (SHORT) (aptl [TXTBOX_TOPRIGHT].x -
  11835.                                       aptl [TXTBOX_TOPLEFT].x) ;
  11836.  
  11837.                   cyString = (SHORT) (aptl [TXTBOX_TOPLEFT].y -
  11838.                                       aptl [TXTBOX_BOTTOMLEFT].y) ;
  11839.  
  11840.                             /*-------------------------------------------
  11841.                                Create bitmap and set it in the memory PS
  11842.                               -------------------------------------------*/
  11843.  
  11844.                   bmp.cbFix     = sizeof bmp ;
  11845.                   bmp.cx        = cxString ;
  11846.                   bmp.cy        = cyString ;
  11847.                   bmp.cPlanes   = 1 ;
  11848.                   bmp.cBitCount = 1 ;
  11849.  
  11850.                   hbm = GpiCreateBitmap (hpsMemory, &bmp, 0L, 0L, NULL) ;
  11851.  
  11852.                   GpiSetBitmap (hpsMemory, hbm) ;
  11853.  
  11854.                             /*----------------------------------------
  11855.                                Write the text string to the memory PS
  11856.                               ----------------------------------------*/
  11857.  
  11858.                   ptl.x = 0 ;
  11859.                   ptl.y = - aptl [TXTBOX_BOTTOMLEFT].y ;
  11860.  
  11861.                   GpiSetColor (hpsMemory, CLR_TRUE) ;
  11862.                   GpiSetBackColor (hpsMemory, CLR_FALSE) ;
  11863.                   GpiSetBackMix (hpsMemory, BM_OVERPAINT) ;
  11864.                   GpiCharStringAt (hpsMemory, &ptl, sizeof szHello - 1L,
  11865.                                    szHello) ;
  11866.                   return 0 ;
  11867.  
  11868.              case WM_SIZE:
  11869.                   cxClient = SHORT1FROMMP (mp2) ;
  11870.                   cyClient = SHORT2FROMMP (mp2) ;
  11871.                   return 0 ;
  11872.  
  11873.              case WM_PAINT:
  11874.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  11875.  
  11876.                   for (y = 0 ; y <= cyClient / cyString ; y++)
  11877.                        for (x = 0 ; x <= cxClient / cxString ; x++)
  11878.                             {
  11879.                             aptl[0].x = x * cxString ;         // target lower
  11880.                             aptl[0].y = y * cyString ;
  11881.  
  11882.                             aptl[1].x = aptl[0].x + cxString ; // upper right
  11883.                             aptl[1].y = aptl[0].y + cyString ;
  11884.  
  11885.                             aptl[2].x = 0 ;                    // source lower
  11886.                             aptl[2].y = 0 ;
  11887.  
  11888.                             GpiBitBlt (hps, hpsMemory, 3L, aptl, ROP_SRCCOPY,
  11889.                                        BBO_AND) ;
  11890.                             }
  11891.                   WinEndPaint (hps) ;
  11892.                   return 0 ;
  11893.  
  11894.              case WM_DESTROY:
  11895.                   GpiDestroyPS (hpsMemory) ;
  11896.                   DevCloseDC (hdcMemory) ;
  11897.                   GpiDeleteBitmap (hbm) ;
  11898.                   return 0 ;
  11899.              }
  11900.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  11901.         }
  11902.  
  11903.    The HELLOBIT.DEF File
  11904.  
  11905.    ;-------------------------------------
  11906.    ; HELLOBIT.DEF module definition file
  11907.    ;-------------------------------------
  11908.  
  11909.    NAME           HELLOBIT  WINDOWAPI
  11910.  
  11911.    DESCRIPTION    '"Hello, world" Bitmap (C) Charles Petzold, 1988'
  11912.    PROTMODE
  11913.    HEAPSIZE       1024
  11914.    STACKSIZE      8192
  11915.    EXPORTS        ClientWndProc
  11916.  
  11917.  
  11918.  HELLOBIT creates a memory device context and a presentation space associated
  11919.  with this memory device context during the WM_CREATE message. Then it
  11920.  determines the dimension of the text "Hello, world" by calling
  11921.  GpiQueryTextBox. The program creates a bitmap of these dimensions and sets
  11922.  the bitmap in the memory device context.
  11923.  
  11924.  HELLOBIT then writes the text string on the presentation space by calling
  11925.  
  11926.  GpiCharStringAt. The bitmap is monochrome, so the foreground color is set to
  11927.  CLR_TRUE (1 bits), and the background color to CLR_FALSE (0 bits). These are
  11928.  the best color values to use with monochrome bitmaps. HELLOBIT also sets the
  11929.  background mix to BM_OVERPAINT so that the background is filled in with 0
  11930.  bits.
  11931.  
  11932.  During the WM_PAINT message, HELLOBIT covers the window with copies of this
  11933.  bitmap by calling GpiBitBlt. This is shown in Figure 6-16.
  11934.  
  11935.  As in BITCAT2, the presentation space, device context, and bitmap are
  11936.  destroyed during the WM_DESTROY message.
  11937.  
  11938.  You might want to try a variation of HELLOBIT that uses WinDrawBitmap during
  11939.  the WM_PAINT message. In this case, you don't need the memory device context
  11940.  or presentation space after you initialize the bitmap by calling
  11941.  GpiCharStringAt. You can destroy them after that call:
  11942.  
  11943.    GpiDestroyPS (hpsMemory) ;
  11944.    DevCloseDC (hdcMemory) ;
  11945.  
  11946.  During the WM_DESTROY message you need only destroy the bitmap. Thus the
  11947.  memory device context and presentation space serve simply as a mold to draw
  11948.  on the bitmap. You can then destroy the mold (calling GpiDestroyPS and
  11949.  DevCloseDC), leaving the bitmap behind.
  11950.  
  11951.  One common use of a memory device context is for a "shadow bitmap." You
  11952.  create a bitmap large enough to encompass the client window and select that
  11953.  into a presentation space associated with a memory device context. Whenever
  11954.  you draw on the window, you also draw on the presentation space. During the
  11955.  WM_PAINT message, you can update the client window with a simple GpiBitBlt
  11956.  call. This approach is shown in the SKETCH program in Chapter 9.
  11957.  
  11958.  When a bitmap is set in a presentation space associated with a memory device
  11959.  context, you can set the bitmap bits with an array of data using
  11960.  GpiSetBitmapBits. This is yet another way to initialize a bitmap. You can
  11961.  also obtain the bitmap bits and store them in an array by calling
  11962.  GpiQueryBitmapBits.
  11963.  
  11964.  Customized Patterns
  11965.  
  11966.  Another use for bitmaps is to create your own customized patterns for area
  11967.  filling. A pattern is based on an 8-by-8 bitmap. When a pattern is used to
  11968.  fill an area, the bitmap is simply repeated horizontally and vertically.
  11969.  
  11970.  For example, suppose you want to use a pattern that looks like a brick wall.
  11971.  Assuming you want the brick itself to be the foreground color (1 bits) and
  11972.  the cement between the bricks to be the background color (0 bits), the
  11973.  bitmap that you begin with might look like this:
  11974.  
  11975.  These bits (padded at the right so each row is 32 bits) are stored in the
  11976.  abBrick array in the BRICKS program in Figure 6-17.
  11977.  
  11978.  Figure 6-17.  The BRICKS program.
  11979.  
  11980.    The BRICKS File
  11981.  
  11982.    #------------------
  11983.    # BRICKS make file
  11984.    #------------------
  11985.  
  11986.    bricks.obj : bricks.c
  11987.         cl -c -G2sw -W3 bricks.c
  11988.  
  11989.    bricks.exe : bricks.obj bricks.def
  11990.         link bricks, /align:16, NUL, os2, bricks
  11991.  
  11992.    The BRICKS.C File
  11993.  
  11994.    /*--------------------------------------------
  11995.       BRICKS.C -- Customized Pattern from Bitmap
  11996.      --------------------------------------------*/
  11997.  
  11998.    #define INCL_WIN
  11999.    #define INCL_GPI
  12000.    #include <os2.h>
  12001.    #include <stdlib.h>
  12002.  
  12003.    #define LCID_BRICKS_BITMAP    1L
  12004.  
  12005.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  12006.  
  12007.    int main (void)
  12008.         {
  12009.  
  12010.         static CHAR  szClientClass [] = "Bricks" ;
  12011.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  12012.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  12013.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  12014.         HAB          hab ;
  12015.         HMQ          hmq ;
  12016.         HWND         hwndFrame, hwndClient ;
  12017.         QMSG         qmsg ;
  12018.  
  12019.         hab = WinInitialize (0) ;
  12020.         hmq = WinCreateMsgQueue (hab, 0) ;
  12021.  
  12022.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  12023.  
  12024.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  12025.                                         &flFrameFlags, szClientClass, NULL,
  12026.                                         0L, NULL, 0, &hwndClient) ;
  12027.  
  12028.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  12029.              WinDispatchMsg (hab, &qmsg) ;
  12030.  
  12031.         WinDestroyWindow (hwndFrame) ;
  12032.         WinDestroyMsgQueue (hmq) ;
  12033.         WinTerminate (hab) ;
  12034.         return 0 ;
  12035.         }
  12036.  
  12037.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  12038.         {
  12039.         static BYTE      abBrick [] = {
  12040.                                       0x00, 0x00, 0x00, 0x00,
  12041.                                       0xF3, 0x00, 0x00, 0x00,
  12042.                                       0xF3, 0x00, 0x00, 0x00,
  12043.                                       0xF3, 0x00, 0x00, 0x00,
  12044.                                       0x00, 0x00, 0x00, 0x00,
  12045.                                       0x3F, 0x00, 0x00, 0x00,
  12046.                                       0x3F, 0x00, 0x00, 0x00,
  12047.                                       0x3F, 0x00, 0x00, 0x00
  12048.                                       } ;
  12049.         static HBITMAP   hbm ;
  12050.         static POINTL    aptl [2] ;
  12051.         BITMAPINFO       *pbmi ;
  12052.         BITMAPINFOHEADER bmp ;
  12053.         HPS              hps ;
  12054.  
  12055.         switch (msg)
  12056.              {
  12057.              case WM_CREATE:
  12058.                                  /*----------------------
  12059.                                     Create 8-by-8 bitmap
  12060.                                    ----------------------*/
  12061.  
  12062.                   bmp.cbFix     = sizeof bmp ;
  12063.                   bmp.cx        = 8 ;
  12064.                   bmp.cy        = 8 ;
  12065.                   bmp.cPlanes   = 1 ;
  12066.                   bmp.cBitCount = 1 ;
  12067.  
  12068.                   pbmi = malloc (sizeof (BITMAPINFO) + sizeof (RGB)) ;
  12069.  
  12070.                   pbmi->cbFix     = sizeof bmp ;
  12071.                   pbmi->cx        = 8 ;
  12072.                   pbmi->cy        = 8 ;
  12073.                   pbmi->cPlanes   = 1 ;
  12074.                   pbmi->cBitCount = 1 ;
  12075.  
  12076.                   pbmi->argbColor[0].bBlue  = 0 ;
  12077.                   pbmi->argbColor[0].bGreen = 0 ;
  12078.                   pbmi->argbColor[0].bRed   = 0 ;
  12079.                   pbmi->argbColor[1].bBlue  = 0xFF ;
  12080.                   pbmi->argbColor[1].bGreen = 0xFF ;
  12081.                   pbmi->argbColor[1].bRed   = 0xFF ;
  12082.  
  12083.                   hps = WinGetPS (hwnd) ;
  12084.                   hbm = GpiCreateBitmap (hps, &bmp, CBM_INIT, abBrick, pbmi) ;
  12085.  
  12086.                   WinReleasePS (hps) ;
  12087.                   free (pbmi) ;
  12088.                   return 0 ;
  12089.  
  12090.              case WM_SIZE:
  12091.                   aptl[1].x = SHORT1FROMMP (mp2) ;
  12092.                   aptl[1].y = SHORT2FROMMP (mp2) ;
  12093.                   return 0 ;
  12094.  
  12095.              case WM_PAINT:
  12096.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  12097.  
  12098.                   GpiSetBitmapId (hps, hbm, LCID_BRICKS_BITMAP) ;
  12099.                   GpiSetPatternSet (hps, LCID_BRICKS_BITMAP) ;
  12100.  
  12101.                   GpiBitBlt (hps, NULL, 2L, aptl, ROP_PATCOPY, BBO_AND) ;
  12102.  
  12103.                   GpiSetPatternSet (hps, LCID_DEFAULT) ;
  12104.                   GpiDeleteSetId (hps, LCID_BRICKS_BITMAP) ;
  12105.  
  12106.                   WinEndPaint (hps) ;
  12107.                   return 0 ;
  12108.  
  12109.              case WM_DESTROY:
  12110.                   GpiDeleteBitmap (hbm) ;
  12111.                   return 0 ;
  12112.              }
  12113.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  12114.         }
  12115.  
  12116.    The BRICKS.DEF File
  12117.  
  12118.    ;-----------------------------------
  12119.    ; BRICKS.DEF module definition file
  12120.    ;-----------------------------------
  12121.  
  12122.    NAME           BRICKS    WINDOWAPI
  12123.  
  12124.    DESCRIPTION    'Customized Pattern from Bitmap (c) Charles Petzold, 1988'
  12125.    PROTMODE
  12126.    HEAPSIZE       1024
  12127.    STACKSIZE      8192
  12128.    EXPORTS        ClientWndProc
  12129.  
  12130.  
  12131.  During the WM_CREATE message, BRICKS creates an 8-by-8 bitmap initialized
  12132.  with these bits. During the WM_PAINT message, the bitmap is first tagged
  12133.  with an ID number:
  12134.  
  12135.    GpiSetBitmapId (hps, hbm, LCID_BRICKS_BITMAP) ;
  12136.  
  12137.  The LCID_BRICKS_BITMAP is defined in BRICKS.C as 1. (LCID stands for "local
  12138.  id.") The bitmap is now an available pattern. You can use the
  12139.  GpiSetPatternSet function to make this pattern the current pattern:
  12140.  
  12141.    GpiSetPatternSet (hps, LCID_BRICKS_BITMAP) ;
  12142.  
  12143.  Notice that this is the GpiSetPatternSet function rather than the
  12144.  GpiSetPattern function normally used for selecting a pattern.
  12145.  
  12146.  BRICKS uses the GpiBitBlt function with the ROP_PATCOPY raster operation to
  12147.  fill the window with the bitmap, as shown in Figure 6-18.
  12148.  
  12149.  The program could also have used GpiBox with the DRO_FILL or DRO_OUTLINEFILL
  12150.  option to display the bricks pattern. After BRICKS uses the pattern, the
  12151.  current pattern set is established as the default pattern set (containing
  12152.  all predefined patterns):
  12153.  
  12154.    GpiSetPatternSet (hps, LCID_DEFAULT) ;
  12155.  
  12156.  The LCID_DEFAULT identifier is defined in PMGPI.H. BRICKS then deletes the
  12157.  local ID:
  12158.  
  12159.    GpiDeleteSetId (hps, LCID_BRICKS_BITMAP) ;
  12160.  
  12161.  You can define up to 254 customized patterns using local IDs ranging from 1
  12162.  through 254. When you want to use a customized pattern, you call
  12163.  GpiSetPatternSet with the local ID. When you want to use a predefined
  12164.  pattern, you first call GpiSetPatternSet with LCID_DEFAULT and then use
  12165.  GpiSetPattern with a PATSYM identifier.
  12166.  
  12167.  The local IDs for these customized patterns become part of the presentation
  12168.  space. Before releasing a cached micro-PS by calling WinReleasePS or
  12169.  WinEndPaint, you should set the pattern to the default and delete all the
  12170.  IDs. A bitmap cannot be deleted while it has a local ID. You can obtain the
  12171.  bitmap handle tagged with a local ID by calling GpiQueryBitmapHandle:
  12172.  
  12173.    hbm = GpiQueryBitmapHandle (hps, lcid) ;
  12174.  
  12175.  You'll recall from the last chapter that you use local IDs when creating
  12176.  fonts. You cannot use the same local ID for both a bitmap and a pattern at
  12177.  the same time. Also keep in mind that the GpiSetPS function deletes all
  12178.  local IDs.
  12179.  
  12180.  Drawing Pixels
  12181.  
  12182.  Finally, GPI has two functions that might seem important when you first
  12183.  begin programming for the OS/2 Presentation Manager. The GpiSetPel function
  12184.  sets an individual pixel to the current foreground color:
  12185.  
  12186.    GpiSetPel (hps, &ptl) ;
  12187.  
  12188.  The GpiQueryPel function obtains the color of an individual pixel:
  12189.  
  12190.    lColor = GpiQueryPel (hps, &ptl) ;
  12191.  
  12192.  These two functions are used so infrequently in Presentation Manager
  12193.  programming that you can just about ignore them.
  12194.  
  12195.  
  12196.  Chapter 7  Advanced VIO: The Easy Way Out
  12197.  ───────────────────────────────────────────────────────────────────────────
  12198.  
  12199.  
  12200.  When OS/2 1.0 was first released, many programmers were favorably impressed
  12201.  by the collection of VIO ("video input/output") functions included in the
  12202.  operating system. These VIO functions gave character-mode OS/2 applications
  12203.  a fast, high-level interface to the video display. Unlike DOS applications,
  12204.  OS/2 1.0 applications can achieve good video performance without directly
  12205.  accessing the video display hardware.
  12206.  
  12207.  The Presentation Manager is not a character-mode environment. It uses other
  12208.  functions (such as GpiCharStringAt) to display text on a window. But the
  12209.  OS/2 VIO functions are too good to abandon entirely. After all, some
  12210.  applications don't need graphics. It seems reasonable that the Presentation
  12211.  Manager allow such applications to write to their client window as if the
  12212.  window were a character-mode device. The AVIO ("Advanced VIO")──an
  12213.  enhancement to the OS/2 1.0 VIO interface, designed specifically for
  12214.  Presentation Manager programs──allows them to do just that.
  12215.  
  12216.  The Presentation Manager intercepts all VIO calls from all applications
  12217.  running in the Presentation Manager session. This serves two purposes.
  12218.  First, it allows many character-mode programs originally written for OS/2
  12219.  1.0 to run in a window. The Presentation Manager intercepts the VIO calls
  12220.  and routes the output to the program's window. Although they cannot take
  12221.  advantage of Presentation Manager features (such as graphics, menus, and
  12222.  dialog boxes), these OS/2 1.0 programs are accessible to users because they
  12223.  run in a window in the Presentation Manager session. (Character-mode
  12224.  programs running in other sessions continue to use the normal, unintercepted
  12225.  VIO interface.)
  12226.  
  12227.  Second, the Presentation Manager also intercepts VIO calls from Presentation
  12228.  Manager programs that use Advanced VIO for displaying text. The text output
  12229.  is converted to graphics and displayed on the window that the program
  12230.  creates.
  12231.  
  12232.  In general, using AVIO is easier than using GPI. Rather than specifying a
  12233.  coordinate position where a text string is to begin, the program specifies a
  12234.  character row and column position relative to the upper-left corner of the
  12235.  client window. The big disadvantage of AVIO is that precise placement of
  12236.  text is not possible. Each character you display is placed in a character
  12237.  cell. All character cells are the same width and height. Proportional
  12238.  spacing and techniques such as subscripting are not possible. However, a
  12239.  Presentation Manager program using AVIO can also display graphics (including
  12240.  text) on the same window by using the normal GPI interface.
  12241.  
  12242.  
  12243.  AVIO Mechanics
  12244.  
  12245.  Let's begin by examining a simple program called AVIO1 (Figure 7-1) that
  12246.  uses several AVIO functions to display the first paragraph of Lewis
  12247.  Carroll's Alice in Wonderland in its client window.
  12248.  
  12249.  Figure 7-1.  The AVIO1 program.
  12250.  
  12251.    The AVIO1 File
  12252.  
  12253.    #-----------------
  12254.    # AVIO1 make file
  12255.    #-----------------
  12256.  
  12257.    avio1.obj : avio1.c
  12258.         cl -c -G2sw -W3 avio1.c
  12259.  
  12260.    avio1.exe : avio1.obj avio1.def
  12261.         link avio1, /align:16, NUL, os2, avio1
  12262.  
  12263.    The AVIO1.C File
  12264.  
  12265.    /*-----------------------------------------
  12266.       AVIO1.C -- Advanced VIO Display of Text
  12267.      -----------------------------------------*/
  12268.  
  12269.    #define INCL_WIN
  12270.    #define INCL_VIO
  12271.    #define INCL_AVIO
  12272.    #include <os2.h>
  12273.    #include <string.h>
  12274.  
  12275.    #define VIDEOWIDTH 40
  12276.  
  12277.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  12278.  
  12279.    HAB  hab ;
  12280.  
  12281.    int main (void)
  12282.         {
  12283.         static CHAR  szClientClass [] = "Avio1" ;
  12284.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
  12285.                                     FCF_SIZEBORDER    | FCF_MINMAX   |
  12286.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  12287.         HMQ          hmq ;
  12288.         HWND         hwndFrame, hwndClient ;
  12289.         QMSG         qmsg ;
  12290.  
  12291.         hab = WinInitialize (0) ;
  12292.         hmq = WinCreateMsgQueue (hab, 0) ;
  12293.  
  12294.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  12295.  
  12296.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  12297.                                         &flFrameFlags, szClientClass, NULL,
  12298.                                         0L, NULL, 0, &hwndClient) ;
  12299.  
  12300.         WinSendMsg (hwndFrame, WM_SETICON,
  12301.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  12302.                     NULL) ;
  12303.  
  12304.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  12305.              WinDispatchMsg (hab, &qmsg) ;
  12306.  
  12307.         WinDestroyWindow (hwndFrame) ;
  12308.         WinDestroyMsgQueue (hmq) ;
  12309.         WinTerminate (hab) ;
  12310.         return 0 ;
  12311.         }
  12312.  
  12313.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  12314.         {
  12315.         static CHAR  *aszAlice [] = {
  12316.                                     "Alice was beginning to get very tired of"
  12317.                                     "sitting by her sister on the bank and of"
  12318.                                     "having nothing to do: once or twice she",
  12319.                                     "had peeped into the book her sister was",
  12320.  
  12321.                                     "reading, but it had no pictures or ",
  12322.                                     "conversations in it, \"and what is the",
  12323.                                     "use of a book,\" thought Alice, \"without
  12324.                                     "pictures or conversations?\""
  12325.                                     } ;
  12326.         static HPS   hps ;
  12327.         static HVPS  hvps ;
  12328.         static SHORT sNumLines = sizeof aszAlice / sizeof aszAlice[0] ;
  12329.         HDC          hdc ;
  12330.         SHORT        sRow ;
  12331.         SIZEL        sizl ;
  12332.  
  12333.         switch (msg)
  12334.              {
  12335.              case WM_CREATE:
  12336.                   hdc = WinOpenWindowDC (hwnd) ;
  12337.  
  12338.                   sizl.cx = sizl.cy = 0 ;
  12339.                   hps = GpiCreatePS (hab, hdc, &sizl, PU_PELS    | GPIF_DEFAUL
  12340.                                                       GPIT_MICRO | GPIA_ASSOC)
  12341.  
  12342.                   VioCreatePS (&hvps, sNumLines, VIDEOWIDTH, 0, 1, NULL) ;
  12343.                   VioAssociate (hdc, hvps) ;
  12344.  
  12345.                   for (sRow = 0 ; sRow < sNumLines ; sRow++)
  12346.                        VioWrtCharStr (aszAlice[sRow],
  12347.                                       strlen (aszAlice[sRow]),
  12348.                                       sRow, 0, hvps) ;
  12349.                   return 0 ;
  12350.  
  12351.              case WM_SIZE:
  12352.                   WinDefAVioWindowProc (hwnd, msg, mp1, mp2) ;
  12353.                   return 0 ;
  12354.  
  12355.              case WM_PAINT:
  12356.                   WinBeginPaint (hwnd, hps, NULL) ;
  12357.                   GpiErase (hps) ;
  12358.  
  12359.                   VioShowBuf (0, 2 * sNumLines * VIDEOWIDTH, hvps) ;
  12360.  
  12361.                   WinEndPaint (hps) ;
  12362.                   return 0 ;
  12363.  
  12364.              case WM_DESTROY:
  12365.                   VioAssociate (NULL, hvps) ;
  12366.                   VioDestroyPS (hvps) ;
  12367.                   GpiDestroyPS (hps) ;
  12368.                   return 0 ;
  12369.              }
  12370.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  12371.         }
  12372.  
  12373.    The AVIO1.DEF File
  12374.  
  12375.    ;----------------------------------
  12376.    ; AVIO1.DEF module definition file
  12377.    ;----------------------------------
  12378.  
  12379.    NAME           AVIO1     WINDOWAPI
  12380.  
  12381.    DESCRIPTION    'Advanced VIO Display of Text (C) Charles Petzold, 1988'
  12382.    HEAPSIZE       1024
  12383.    STACKSIZE      8192
  12384.    EXPORTS        ClientWndProc
  12385.  
  12386.  
  12387.  The AVIO1 window is shown in Figure 7-2.
  12388.  
  12389.  You'll notice a little problem with AVIO1: The text displayed by the program
  12390.  appears as light gray letters on a black background. This is, of course, the
  12391.  default for text in a character-mode session. However, the rest of the
  12392.  window has a white background (or whatever color you set for the window
  12393.  background in the Presentation Manager Control Program). We'll take care of
  12394.  this problem later.
  12395.  
  12396.  You'll notice that the AVIO1.C file begins with the definition of two
  12397.  identifiers:
  12398.  
  12399.    #define INCL_VIO
  12400.    #define INCL_AVIO
  12401.  
  12402.  The first identifier enables the declaration of OS/2 1.0 VIO functions and
  12403.  structures in the BSESUB.H header file. The second identifier causes the
  12404.  PMAVIO.H header file to be included; this file declares the additional
  12405.  functions supported by the Presentation Manager AVIO interface.
  12406.  
  12407.  Creating and Destroying
  12408.  
  12409.  To use AVIO, a program first creates an AVIO presentation space by calling
  12410.  VioCreatePS. A program that uses AVIO for most of its window output will
  12411.  probably create the presentation space while processing the WM_CREATE
  12412.  message in the window procedure. The general syntax of VioCreatePS is
  12413.  
  12414.    VioCreatePS (&hvps, cyDisplay, cxDisplay, 0, cbAttrs, 0) ;
  12415.  
  12416.  ───────────────────────────────────────────────────────────────────────────
  12417.  NOTE:
  12418.     In the initial release of the Presentation Manager, the fourth and last
  12419.     parameters must be set to 0.
  12420.  ───────────────────────────────────────────────────────────────────────────
  12421.  
  12422.  The first parameter is a pointer to a variable of type HVPS ("handle to a
  12423.  VIO presentation space"), which you can define like this:
  12424.  
  12425.    HVPS hvps ;
  12426.  
  12427.  The VioCreatePS function stores the handle to the AVIO presentation space in
  12428.  this variable. You use the hvps handle with the VIO functions in the same
  12429.  way that you use the hps handle with the GPI functions, except that hvps is
  12430.  the last parameter to the VIO functions rather than the first parameter.
  12431.  
  12432.  When you create an AVIO presentation space, the Presentation Manager creates
  12433.  a "virtual display buffer" for you. This is simply a block of memory
  12434.  organized into rows and columns of characters. You specify the dimensions of
  12435.  this virtual display buffer by using the parameters cxDisplay (number of
  12436.  characters per row) and cyDisplay (number of rows).
  12437.  
  12438.  The cbAttrs parameter must be either 1 or 3. This specifies the number of
  12439.  "attribute" bytes associated with each character. When cbAttrs is set to 1,
  12440.  each character has 1 attribute byte. Within the virtual display buffer, the
  12441.  character and attribute bytes alternate (character first and then
  12442.  attribute). The attribute is compatible with the attribute byte used in
  12443.  character modes of the IBM Color Graphics Adapter (CGA), Enhanced Graphics
  12444.  Adapter (EGA), and Video Graphics Array (VGA). The attribute byte indicates
  12445.  the foreground and background colors of the character. (I'll discuss the
  12446.  alternative use of 3 attribute bytes later in this chapter.)
  12447.  
  12448.  The total number of bytes required for the AVIO virtual display buffer is
  12449.  the product of cxDisplay, cyDisplay, and the number of bytes associated with
  12450.  each character (2 if cbAttrs is set to 1, or 4 if cbAttrs is set to 3). The
  12451.  total size of the AVIO virtual display buffer cannot exceed 65,536 bytes.
  12452.  Once you create an AVIO presentation space, you cannot change the dimensions
  12453.  or number of attribute bytes.
  12454.  
  12455.  When first created, the virtual display buffer is cleared to simulate a
  12456.  blank screen. The character bytes are set to spaces, and the attribute bytes
  12457.  are set to 7, which is light gray text on a black background.
  12458.  
  12459.  The AVIO1 program in Figure 7-1 needs to display only sNumLines rows of
  12460.  text with a maximum width of VIDEOWIDTH (defined in the beginning of the
  12461.  AVIO1.C file). Thus the program creates a presentation space just large
  12462.  enough for this output:
  12463.  
  12464.    VioCreatePS (&hvps, sNumLines, VIDEOWIDTH, 0, 1, NULL) ;
  12465.  
  12466.  You'll notice that the VioCreatePS function has no parameter to specify the
  12467.  window for which the AVIO presentation space is to be used. After a program
  12468.  creates an AVIO presentation space, it must "associate" the presentation
  12469.  space with a screen device context for a particular window. As you saw in
  12470.  Chapter 5, you can obtain a device context for a window by calling
  12471.  
  12472.    hdc = WinOpenWindowDC (hwnd) ;
  12473.  
  12474.  You then associate the AVIO presentation space with this device context:
  12475.  
  12476.    VioAssociate (hdc, hvps) ;
  12477.  
  12478.  Before your program terminates, it should disassociate the AVIO presentation
  12479.  space from the device context:
  12480.  
  12481.    VioAssociate (NULL, hvps) ;
  12482.  
  12483.  and destroy the presentation space:
  12484.  
  12485.    VioDestroyPS (hvps) ;
  12486.  
  12487.  You'll probably do this during the WM_DESTROY message.
  12488.  
  12489.  AVIO1 also creates a micro-PS during the WM_CREATE message. This
  12490.  presentation space is associated with the same device context as the AVIO
  12491.  presentation space:
  12492.  
  12493.    hps = GpiCreatePS (hab, hdc, &sizl, PU_PELS | GPIF_DEFAULT |
  12494.                                        GPIT_MICRO | GPIA_ASSOC) ;
  12495.  
  12496.  This micro-PS is also destroyed during the WM_DESTROY message:
  12497.  
  12498.    GpiDestroyPS (hps) ;
  12499.  
  12500.  Creating this micro-PS is not strictly required, but it helps out during
  12501.  WM_PAINT processing.
  12502.  
  12503.  Writing to the Presentation Space
  12504.  
  12505.  OS/2 1.0 has seven VIO functions that let you display text on the screen. A
  12506.  Presentation Manager program can use these same seven functions to display
  12507.  text on the AVIO virtual display buffer. The function used in AVIO1.C is
  12508.  VioWrtCharStr. (The other functions are discussed later in this chapter.)
  12509.  
  12510.  The general syntax of VioWrtCharStr is
  12511.  
  12512.    VioWrtCharStr (&chString, cbString, usRow, usCol, hvps) ;
  12513.  
  12514.  The first parameter is a pointer to a character string; the second parameter
  12515.  is to the length of this string. The usRow and usCol parameters indicate the
  12516.  character row and column in which the first character of the string will be
  12517.  displayed. The top row is row 0; the leftmost column is column 0.
  12518.  
  12519.  You can write to the AVIO virtual display buffer any time after you create
  12520.  it. (Because the text in AVIO1's window does not change, AVIO1 does this
  12521.  during the WM_CREATE message.) However, when you write to the AVIO virtual
  12522.  display buffer during the WM_CREATE message, the text is not displayed on
  12523.  the program's window because the window is not yet visible. You must still
  12524.  update the window during the WM_PAINT message.
  12525.  
  12526.  Updating the Window
  12527.  
  12528.  To display text on the window, you must update the window from the AVIO
  12529.  virtual display buffer. Two functions do this: VioShowBuf, which was
  12530.  included in OS/2 1.0, and VioShowPS, which is new with the Presentation
  12531.  Manager.
  12532.  
  12533.  The AVIO1 program uses VioShowBuf, which has a general syntax as follows:
  12534.  
  12535.    VioShowBuf (usByteOffset, usLength, hvps) ;
  12536.  
  12537.  The function updates usLength bytes beginning at the usByteOffset from the
  12538.  start of the virtual display buffer.
  12539.  
  12540.  When the window is visible, you can call VioShowPS or VioShowBuf at any time
  12541.  to update the window. However, as you saw in previous chapters, it's often
  12542.  best to organize your programs so they write to the window only during the
  12543.  WM_PAINT message. Here's how AVIO1 does it:
  12544.  
  12545.    case WM_PAINT:
  12546.         WinBeginPaint (hwnd, hps, NULL) ;
  12547.         GpiErase (hps) ;
  12548.         VioShowBuf (0, 2 * sNumLines*VIDEOWIDTH, hvps) ;
  12549.         WinEndPaint (hps) ;
  12550.  
  12551.         return 0 ;
  12552.  
  12553.  Note that the second parameter to WinEndPaint is the micro-PS handle
  12554.  obtained during WM_CREATE. The presentation space handle is associated with
  12555.  the device context for the window. The AVIO presentation space is also
  12556.  associated with the same device context. This ensures that any updating of
  12557.  the window affects only the invalid area. Note that you can also display
  12558.  graphics to the window by using the hps handle in GPI calls.
  12559.  
  12560.  If you want to restrict the VioShowBuf call to the invalid rectangle of the
  12561.  window (which is often more efficient), you can pass a pointer to a RECTL
  12562.  structure as the last parameter of WinBeginPaint. You must then convert the
  12563.  pixel coordinates of this rectangle into character rows and columns before
  12564.  calling VioShowBuf. (As I'll discuss later in this chapter, you can obtain
  12565.  the dimensions of the AVIO character cell by calling VioGetDeviceCellSize.)
  12566.  
  12567.  Processing WM_SIZE Messages
  12568.  
  12569.  Every window procedure that uses an AVIO presentation space must pass the
  12570.  WM_SIZE message to WinDefAVioWindowProc. You can do your own WM_SIZE
  12571.  processing before or after this call:
  12572.  
  12573.    case WM_SIZE:
  12574.  
  12575.              ....
  12576.  
  12577.         WinDefAVioWindowProc (hwnd, msg, mp1, mp2) ;
  12578.  
  12579.              ....
  12580.  
  12581.         return 0 ;
  12582.  
  12583.  
  12584.  The AVIO Presentation Space
  12585.  
  12586.  When you first encountered the GPI presentation space, the concept may have
  12587.  been somewhat difficult to grasp. You eventually saw how the presentation
  12588.  space is associated with a device context and how it stores various
  12589.  attributes that affect how the GPI functions work on the device.
  12590.  
  12591.  The AVIO presentation space is conceptually much simpler. The most important
  12592.  part of the AVIO presentation space is the virtual display buffer that you
  12593.  write on when you call VIO functions. The presentation space also includes a
  12594.  current cursor position, cursor shape, and origin, as well as other
  12595.  information.
  12596.  
  12597.  The Virtual Display Buffer
  12598.  
  12599.  When you create an AVIO presentation space, you define the row and column
  12600.  dimensions of a display surface and the number of attribute bytes associated
  12601.  with each character. The Presentation Manager allocates a block of memory to
  12602.  use for the virtual display buffer.
  12603.  
  12604.  You can think of this virtual display buffer as comprising a series of
  12605.  cells. Each cell is either 2 or 4 bytes long and comprises a character and 1
  12606.  or 3 attribute bytes, depending on the cbAttrs parameter in the VioCreatePS
  12607.  function.
  12608.  
  12609.  For example, an AVIO presentation space of 5 rows and 10 columns has a
  12610.  virtual display buffer either 100 or 200 bytes long. The buffer begins with
  12611.  the cell in the upper-left corner (row 0 and column 0) and continues with
  12612.  the cells in the first row. The other rows follow. This organization is
  12613.  shown in Figure 7-3.
  12614.  
  12615.  Figure 7-3.  Organization of cells in the AVIO virtual display buffer that
  12616.               has 5 rows and 10 cells per row.
  12617.  
  12618.                                    Column
  12619.                 0    1    2    3    4    5    6    7    8    9
  12620.              ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
  12621.           0  │  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │
  12622.              ├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
  12623.           1  │ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │ 16 │ 17 │ 18 │ 19 │
  12624.              ├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
  12625.      Row  2  │ 20 │ 21 │ 22 │ 23 │ 24 │ 25 │ 26 │ 27 │ 28 │ 29 │
  12626.              ├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
  12627.           3  │ 30 │ 31 │ 32 │ 33 │ 34 │ 35 │ 36 │ 37 │ 38 │ 39 │
  12628.              ├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
  12629.           4  │ 40 │ 41 │ 42 │ 43 │ 44 │ 45 │ 46 │ 47 │ 48 │ 49 │
  12630.              └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
  12631.  
  12632.  
  12633.  Character and Attributes
  12634.  
  12635.  When creating the presentation space, you have a choice of using 1 or 3
  12636.  attribute bytes per cell.
  12637.  
  12638.  The option of 1 attribute byte provides compatibility with character modes
  12639.  of the IBM CGA, EGA, and VGA. Hence, the organization of the virtual display
  12640.  buffer is familiar to any programmer who has worked with character-mode
  12641.  video output on PC compatibles under MS-DOS or OS/2. Each cell is 2 bytes
  12642.  long. The first byte is the character, and the second is the attribute. This
  12643.  is shown in Figure 7-4. The byte offset is measured from the beginning of
  12644.  the virtual display buffer.
  12645.  
  12646.  Figure 7-4.  The organization of characters and attributes when using 1
  12647.               attribute byte.
  12648.  
  12649.         Byte Offset:   0    1    2    3    4    5   ∙∙∙
  12650.                     ┌────┬────┬────┬────┬────┬────┐
  12651.                     │Char│Attr│Char│Attr│Char│Attr│
  12652.                     │  0 │  0 │  1 │  1 │  2 │  2 │ ∙∙∙
  12653.                     └────┴────┴────┴────┴────┴────┘
  12654.  
  12655.  
  12656.  The attribute byte comprises two 4-bit color codes, as shown in Figure 7-5.
  12657.  The lower 4 bits indicate the character color, and the upper 4 bits indicate
  12658.  the background color.
  12659.  
  12660.  Figure 7-5.  The AVIO attribute byte.
  12661.  
  12662.                    ┌───┬───┬───┬───┬───┬───┬───┬───┐
  12663.                    │ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
  12664.                    └───┴───┴───┴───┴───┴───┴───┴───┘
  12665.                    └───────┬───────╨───────┬───────┘
  12666.                       Background       Character
  12667.                          Color           Color
  12668.  
  12669.  
  12670.  These 4-bit color values describe a color based on an IRGB
  12671.  (Intensity-Red-Green-Blue) encoding as shown in Figure 7-6.
  12672.  
  12673.  Figure 7-6.  The color values used in the AVIO attribute byte.
  12674.  
  12675.       ┌─── AVIO Colors ────┐    Traditional
  12676.        I   R   G   B   Value    Color Names     Equivalent GPI Color
  12677.        0   0   0   0    0       Black           CLR_BLACK
  12678.        0   0   0   1    1       Blue            CLR_BLUE
  12679.        0   0   1   0    2       Green           CLR_DARKGREEN
  12680.        0   0   1   1    3       Cyan            CLR_DARKCYAN
  12681.        0   1   0   0    4       Red             CLR_DARKRED
  12682.        0   1   0   1    5       Magenta         CLR_DARKPINK
  12683.        0   1   1   0    6       Brown           CLR_BROWN
  12684.        0   1   1   1    7       Light gray      CLR_PALEGRAY
  12685.        1   0   0   0    8       Dark gray       CLR_DARKGRAY
  12686.        1   0   0   1    9       Light blue      CLR_BLUE
  12687.        1   0   1   0   10       Light green     CLR_GREEN
  12688.        1   0   1   1   11       Light cyan      CLR_CYAN
  12689.        1   1   0   0   12       Light red       CLR_RED
  12690.        1   1   0   1   13       Light magenta   CLR_PINK
  12691.        1   1   1   0   14       Light yellow    CLR_YELLOW
  12692.        1   1   1   1   15       White           CLR_WHITE
  12693.  
  12694.  For example, an attribute byte of 0x1E is yellow text (14 or 0xE) on a blue
  12695.  background (1). These color values do not correspond to the values of the
  12696.  GPI color identifiers, so don't use GPI color indices (like CLR_BLUE) for
  12697.  attribute bytes.
  12698.  
  12699.  ───────────────────────────────────────────────────────────────────────────
  12700.  NOTE:
  12701.     Programmers familiar with character-mode programming will note that the
  12702.     interpretation of the attribute is not exactly the same as in the default
  12703.     operation of the CGA, EGA, and VGA. By default, these video boards
  12704.     interpret the high bit of the attribute as a "blinking" bit rather than
  12705.     as an intensity of the background color. Advanced VIO does not support
  12706.     blinking characters.
  12707.  ───────────────────────────────────────────────────────────────────────────
  12708.  
  12709.  When you set the cbAttrs parameter in VioCreatePS to 3, each cell has 4
  12710.  bytes. The first byte is the character, the second is the normal attribute
  12711.  described above, and the third is an extended attribute byte. This is shown
  12712.  in Figure 7-7. The fourth byte of each cell is used internally by the
  12713.  Presentation Manager for double-byte character set support.
  12714.  
  12715.  Figure 7-7.  The organization of characters and attributes with the use of
  12716.               3 attribute bytes.
  12717.  
  12718.         Byte Offset:   0    1    2    3    4    5    6    7   ∙∙∙
  12719.                     ┌────┬────┬────┬────┬────┬────┬────┬────┐
  12720.                     │Char│Attr│Ext.│    │Char│Attr│Ext.│    │
  12721.                     │    │    │Attr│    │    │    │Attr│    │
  12722.                     │  0 │  0 │  0 │    │  1 │  1 │  1 │    │ ∙∙∙
  12723.                     └────┴────┴────┴────┴────┴────┴────┴────┘
  12724.  
  12725.  
  12726.  The extended attribute byte is shown in Figure 7-8.
  12727.  
  12728.  Figure 7-8.  The AVIO extended attribute byte.
  12729.  
  12730.          ┌───┬───┬───┬───┬───┬───┬───┬───┐
  12731.          │ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
  12732.          └───┴───┴───┴───┴───┴───┴───┴───┘
  12733.            │   │       │         └───┬───┘
  12734.            │   │       │             └──── Font ID (0 for default font)
  12735.            │   │       └────────────────── 1 for transparent background
  12736.            │   └────────────────────────── 1 for reverse video
  12737.            └────────────────────────────── 1 for underscore
  12738.  
  12739.  
  12740.  When you set bit 4 to 1, the background of the character cell is not colored
  12741.  by the background color. Whatever was there remains there. (This is useful
  12742.  when you mix AVIO text and graphics.) When bit 6 is set to 1, foreground and
  12743.  background colors are switched for the character. Bit 7 underlines the
  12744.  character.
  12745.  
  12746.  Writing Directly to the Buffer
  12747.  
  12748.  When you call functions such as VioWrtCharStr to display text on an AVIO
  12749.  presentation space, the text is stored in the virtual display buffer and, if
  12750.  possible, displayed on the window.
  12751.  
  12752.  You can also write directly on the virtual display buffer. To do this, you
  12753.  must first obtain a far pointer to the beginning of the buffer by calling
  12754.  VioGetBuf. Your program can then write on the buffer using normal C pointer
  12755.  manipulation. But this text will not automatically be displayed on the
  12756.  window. You must update the window from the buffer with VioShowPS or
  12757.  VioShowBuf.
  12758.  
  12759.  You need to define a few variables in preparation for calling VioGetBuf:
  12760.  
  12761.    PCHAR pVideoBuffer ;
  12762.    ULONG ulVideoBuffer ;
  12763.    ULONG usVideoLength ;
  12764.  
  12765.  You call VioGetBuf like this:
  12766.  
  12767.    VioGetBuf (&ulVideoBuffer, &usVideoLength, hvps) ;
  12768.  
  12769.  On return from the function, ulVideoBuffer is a far pointer to the beginning
  12770.  of the logical video buffer stored as a ULONG integer. The returned value of
  12771.  usVideoLength is the length of the buffer in bytes. You already know this
  12772.  length: It's the number of rows times the number of columns times the number
  12773.  of bytes per cell (2 or 4).
  12774.  
  12775.  The first parameter to VioGetBuf is declared in the BSESUB.H header file as
  12776.  a pointer to a ULONG value, but it's really a pointer to a PCHAR (which
  12777.  itself is a far pointer to CHAR), so you can cast it into PCHAR like this:
  12778.  
  12779.    pVideoBuffer = (PCHAR) ulVideoBuffer ;
  12780.  
  12781.  Let's assume that cxDisplay is the width of the AVIO presentation space and
  12782.  cbAttrs is the number of attribute bytes. If you wanted to write the letter
  12783.  A in the sRow and sCol position of the virtual display buffer, you would use
  12784.  the following syntax:
  12785.  
  12786.    *(pVideoBuffer + sRow * cxDisplay * (cbAttrs + 1) + sCol) = 'A' ;
  12787.  
  12788.  Of course, this syntax is somewhat clumsy for general use. You will probably
  12789.  want to define a macro that makes the code a little clearer.
  12790.  
  12791.  Or, you could use the approach shown in the AVIO2 program in Figure 7-9.
  12792.  
  12793.  Figure 7-9.  The AVIO2 program.
  12794.  
  12795.    The AVIO2 File
  12796.  
  12797.    #-----------------
  12798.    # AVIO2 make file
  12799.    #-----------------
  12800.  
  12801.    avio2.obj : avio2.c
  12802.         cl -c -G2sw -W3 avio2.c
  12803.  
  12804.    avio2.exe : avio2.obj avio2.def
  12805.         link avio2, /align:16, NUL, os2, avio2
  12806.  
  12807.    The AVIO2.C File
  12808.  
  12809.    /*-----------------------------------------
  12810.       AVIO2.C -- Advanced VIO Display of Text
  12811.      -----------------------------------------*/
  12812.  
  12813.    #define INCL_WIN
  12814.    #define INCL_VIO
  12815.    #define INCL_AVIO
  12816.    #include <os2.h>
  12817.  
  12818.    #define VIDEOWIDTH 40
  12819.  
  12820.    typedef struct
  12821.         {
  12822.         CHAR ch ;
  12823.         CHAR attr ;
  12824.         }
  12825.         VIDEO [][VIDEOWIDTH] ;
  12826.  
  12827.    typedef VIDEO FAR *PVIDEO ;
  12828.  
  12829.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  12830.  
  12831.    HAB  hab ;
  12832.  
  12833.    int main (void)
  12834.         {
  12835.         static CHAR    szClientClass [] = "Avio2" ;
  12836.         static ULONG   flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  12837.                                       FCF_SIZEBORDER    | FCF_MINMAX  |
  12838.                                       FCF_SHELLPOSITION | FCF_TASKLIST ;
  12839.         HMQ            hmq ;
  12840.         HWND           hwndFrame, hwndClient ;
  12841.         QMSG           qmsg ;
  12842.  
  12843.         hab = WinInitialize (0) ;
  12844.         hmq = WinCreateMsgQueue (hab, 0) ;
  12845.  
  12846.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  12847.  
  12848.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  12849.                                         &flFrameFlags, szClientClass, NULL,
  12850.                                         0L, NULL, 0, &hwndClient) ;
  12851.  
  12852.         WinSendMsg (hwndFrame, WM_SETICON,
  12853.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  12854.                     NULL) ;
  12855.  
  12856.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  12857.              WinDispatchMsg (hab, &qmsg) ;
  12858.  
  12859.         WinDestroyWindow (hwndFrame) ;
  12860.         WinDestroyMsgQueue (hmq) ;
  12861.         WinTerminate (hab) ;
  12862.         return 0 ;
  12863.         }
  12864.  
  12865.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  12866.         {
  12867.         static CHAR  *aszAlice [] = {
  12868.                                     "Alice was beginning to get very tired of"
  12869.                                     "sitting by her sister on the bank and of"
  12870.                                     "having nothing to do: once or twice she",
  12871.                                     "had peeped into the book her sister was",
  12872.                                     "reading, but it had no pictures or",
  12873.                                     "conversations in it, \"and what is the",
  12874.                                     "use of a book,\" thought Alice, \"without
  12875.                                     "pictures or conversations?\""
  12876.                                     } ;
  12877.         static HPS    hps ;
  12878.         static HVPS   hvps ;
  12879.         static PVIDEO pvideo ;
  12880.         static SHORT  sNumLines = sizeof aszAlice / sizeof aszAlice[0] ;
  12881.         static USHORT usVideoLength ;
  12882.         HDC           hdc ;
  12883.         RECTL         rcl ;
  12884.         SHORT         sRow, sCol ;
  12885.         SIZEL         sizl ;
  12886.         ULONG         ulVideoBuffer ;
  12887.  
  12888.         switch (msg)
  12889.              {
  12890.              case WM_CREATE:
  12891.                   hdc = WinOpenWindowDC (hwnd) ;
  12892.  
  12893.                   sizl.cx = sizl.cy = 0 ;
  12894.                   hps = GpiCreatePS (hab, hdc, &sizl, PU_PELS    | GPIF_DEFAUL
  12895.                                                       GPIT_MICRO | GPIA_ASSOC)
  12896.  
  12897.                   VioCreatePS (&hvps, sNumLines, VIDEOWIDTH, 0, 1, NULL) ;
  12898.                   VioAssociate (hdc, hvps) ;
  12899.  
  12900.                   VioGetBuf (&ulVideoBuffer, &usVideoLength, hvps) ;
  12901.                   pvideo = (PVIDEO) ulVideoBuffer ;
  12902.  
  12903.                   for (sRow = 0 ; sRow < sNumLines ; sRow++)
  12904.                        for (sCol = 0 ; sCol < VIDEOWIDTH ; sCol++)
  12905.                             (*pvideo) [sRow][sCol].attr = '\x1E' ;
  12906.  
  12907.                   for (sRow = 0 ; sRow < sNumLines ; sRow++)
  12908.                        for (sCol = 0 ; aszAlice [sRow][sCol] ; sCol++)
  12909.                             (*pvideo) [sRow][sCol].ch = aszAlice [sRow][sCol]
  12910.  
  12911.                   return 0 ;
  12912.  
  12913.              case WM_SIZE:
  12914.                   WinDefAVioWindowProc (hwnd, msg, mp1, mp2) ;
  12915.                   return 0 ;
  12916.  
  12917.              case WM_PAINT:
  12918.                   WinBeginPaint (hwnd, hps, NULL) ;
  12919.  
  12920.                   WinQueryWindowRect (hwnd, &rcl) ;
  12921.                   WinFillRect (hps, &rcl, CLR_DARKBLUE) ;
  12922.  
  12923.                   VioShowBuf (0, usVideoLength, hvps) ;
  12924.  
  12925.                   WinEndPaint (hps) ;
  12926.                   return 0 ;
  12927.  
  12928.              case WM_DESTROY:
  12929.                   VioAssociate (NULL, hvps) ;
  12930.                   VioDestroyPS (hvps) ;
  12931.                   GpiDestroyPS (hps) ;
  12932.                   return 0 ;
  12933.              }
  12934.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  12935.         }
  12936.  
  12937.    The AVIO2.DEF File
  12938.  
  12939.    ;----------------------------------
  12940.    ; AVIO2.DEF module definition file
  12941.    ;----------------------------------
  12942.  
  12943.    NAME           AVIO2     WINDOWAPI
  12944.  
  12945.    DESCRIPTION    'Advanced VIO Display of Text (C) Charles Petzold, 1988'
  12946.    HEAPSIZE       1024
  12947.    STACKSIZE      8192
  12948.    EXPORTS        ClientWndProc
  12949.  
  12950.  
  12951.  The method shown here works only when the width of the AVIO presentation
  12952.  space is a constant and is known during compilation of the program.
  12953.  
  12954.  If you're using 1 attribute byte and VIDEOWIDTH is the width of the AVIO
  12955.  presentation space, you can define a new data type called VIDEO like this:
  12956.  
  12957.    typedef struct
  12958.         {
  12959.         CHAR ch ;
  12960.         CHAR attr ;
  12961.         }
  12962.         VIDEO [][VIDEOWIDTH] ;
  12963.  
  12964.  The data type VIDEO is a two-dimensional array of structures. The first
  12965.  dimension is the row, and the second is the column. Define another new data
  12966.  type called PVIDEO that is a far pointer to VIDEO:
  12967.  
  12968.    typedef VIDEO FAR *PVIDEO ;
  12969.  
  12970.  Within the window procedure, you define the variable pvideo as a PVIDEO (a
  12971.  pointer to a VIDEO array of structures):
  12972.  
  12973.    static PVIDEO pvideo ;
  12974.  
  12975.  The VioGetBuf call is the same as shown above, but now cast ulVideoBuffer
  12976.  into pvideo:
  12977.  
  12978.    pvideo = (PVIDEO) ulVideoBuffer ;
  12979.  
  12980.  To store an 'A' in the sRow row and sCol column, you use:
  12981.  
  12982.    (*pvideo)[sRow][sCol].ch = 'A' ;
  12983.  
  12984.  To set the attribute to 0x1E (yellow text on a blue background), use:
  12985.  
  12986.    (*pvideo)[sRow][sCol].attr = '\x1E' ;
  12987.  
  12988.  AVIO2 initializes the virtual display buffer using two sets of for loops.
  12989.  The first sets all the attributes of the virtual display buffer to \x1E. The
  12990.  second transfers the text into the character positions of the buffer.
  12991.  
  12992.  Before updating the window during the WM_PAINT message, AVIO2 erases the
  12993.  background of its window using WinFillRect rather than GpiErase:
  12994.  
  12995.    WinQueryWindowRect (hwnd, &rcl) ;
  12996.    WinFillRect (hps, &rcl, CLR_DARKBLUE) ;
  12997.  
  12998.  Thus the background of the window is the same as the background of the
  12999.  character cells. This is one way to take care of the color difference
  13000.  between AVIO and GPI.
  13001.  
  13002.  The Size of the Display Buffer
  13003.  
  13004.  Before calling VioCreatePS, you must determine the row and column dimensions
  13005.  of the virtual display buffer. The entire size of this buffer (rows times
  13006.  columns times number of bytes per cell) cannot exceed 65,536 bytes.
  13007.  
  13008.  You'll probably find that an application using AVIO fits into one of the
  13009.  following four categories:
  13010.  
  13011.    1. Programs that require a virtual display buffer smaller than the size of
  13012.       the screen and that probably use a fixed-size window to display the
  13013.       entire buffer at all times. AVIO1 and AVIO2 are examples of this type
  13014.       of program, although the window is not a fixed size. (Fixed-size
  13015.       windows are demonstrated in the FREEMEM and DIGCLOCK programs shown
  13016.       in Chapter 10.)
  13017.  
  13018.    2. Programs that require a buffer of a constant size that may be larger
  13019.       than the size of the screen. The SYSVALS4 program, shown later in this
  13020.       chapter, is of this type. The program provides a facility to scroll the
  13021.       presentation space within the window.
  13022.  
  13023.    3. Programs that use a virtual display buffer to display part of a
  13024.       document. The buffer should be large enough to encompass the entire
  13025.       client window when the window is maximized. The output is adjusted to
  13026.       the window size. A character-mode word-processing or spreadsheet
  13027.       program fits this category.
  13028.  
  13029.    4. Programs that use a teletype output mode to display character data.
  13030.       This category might include modem communications programs.
  13031.  
  13032.  For categories 1 and 2, the size of the presentation space can be easily
  13033.  determined by the program before it calls the VioCreatePS function. Category
  13034.  3 programs must determine the number of character rows and columns that can
  13035.  be displayed when the window is maximized. You can do this by calling
  13036.  WinQuerySysValue with both the CV_CXFULLSCREEN and SV_CYFULLSCREEN
  13037.  parameters to get the pixel dimensions of the maximized client window, and
  13038.  dividing by the AVIO character cell width and height that you obtain by
  13039.  calling VioGetDeviceCellSize (described later in this chapter).
  13040.  
  13041.  With a category 4 program (such as a modem communications program), you
  13042.  probably want a width of 80 columns and a maximum number of lines. (With the
  13043.  1-byte attributes, the AVIO presentation space can be 409 lines long.) This
  13044.  lets you retain and scroll back information that has scrolled past the top
  13045.  of the window.
  13046.  
  13047.  Other Presentation Space Data
  13048.  
  13049.  Besides the virtual display buffer, the AVIO presentation space retains
  13050.  other data, including
  13051.  
  13052.    ■  The origin (the character row and column displayed in the upper left
  13053.       corner of the window)
  13054.  
  13055.    ■  The row and column cursor position
  13056.  
  13057.    ■  The cursor size and whether it's displayed or hidden
  13058.  
  13059.    ■  The dimension in pixels of the character cell
  13060.  
  13061.    ■  The code page (or character set)
  13062.  
  13063.    ■  Font information
  13064.  
  13065.  You can query or change these attributes using various VIO and AVIO
  13066.  functions that I'll describe in the next section.
  13067.  
  13068.  
  13069.  The VIO and AVIO Functions
  13070.  
  13071.  Advanced VIO supports only a subset of the OS/2 1.0 VIO functions. These are
  13072.  listed in Figure 7-10 along with additional AVIO functions available under
  13073.  the Presentation Manager.
  13074.  
  13075.  Figure 7-10.  VIO functions available to a Presentation Manager program.
  13076.  
  13077.       OS/2 1.0 VIO Functions Usable in        Additional Presentation
  13078.       a Presentation Manager Program          Manager AVIO Functions
  13079.  
  13080.       VioGetAnsi        VioSetAnsi            VioAssociate
  13081.       VioGetBuf         VioSetCp              VioCreateLogFont
  13082.       VioGetConfig      VioSetCurPos          VioCreatePS
  13083.       VioGetCp          VioSetCurType         VioDeleteSetId
  13084.       VioGetCurPos      VioShowBuf            VioDestroyPS
  13085.       VioGetCurType     VioWrtCellStr         VioGetDeviceCellSize
  13086.       VioReadCellStr    VioWrtCharStr         VioGetOrg
  13087.       VioReadCharStr    VioWrtCharStrAtt      VioQueryFonts
  13088.       VioScrollDn       VioWrtNAttr           VioQuerySetIds
  13089.       VioScrollLf       VioWrtNCell           VioSetDeviceCellSize
  13090.       VioScrollRt       VioWrtNChar           VioSetOrg
  13091.       VioScrollUp       VioWrtTTY             VioShowPS
  13092.  
  13093.  
  13094.  The following sections describe how the VIO functions work in the AVIO
  13095.  presentation space. The functions are grouped according to purpose:
  13096.  
  13097.    ■  Cursor Position and Size Functions
  13098.    ■  Output Functions
  13099.    ■  ANSI Control Sequences
  13100.    ■  Input Functions
  13101.    ■  Scrolling Functions
  13102.    ■  Origin Functions
  13103.    ■  Cell Size Functions
  13104.    ■  Virtual Display Buffer Functions
  13105.    ■  Miscellaneous VIO Functions
  13106.  
  13107.  Except for the VioCreatePS function, all VIO functions require the hvps
  13108.  handle as the last parameter.
  13109.  
  13110.  Cursor Position and Size Functions
  13111.  
  13112.  AVIO displays a cursor only if the program calls VioWrtTTY (described
  13113.  shortly). When you first create an AVIO presentation space, the cursor is
  13114.  positioned at the upper-left corner (row 0, column 0). You can change the
  13115.  cursor position by calling
  13116.  
  13117.    VioSetCurPos (usRow, usCol, hvps) ;
  13118.  
  13119.  You do not need to call VioShowBuf or VioShowPS to display the new cursor
  13120.  position.
  13121.  
  13122.  To obtain the current position of the cursor, call
  13123.  
  13124.    VioGetCurPos (&usRow, &usCol, hvps) ;
  13125.  
  13126.  Note that this function requires two pointers to variables that receive the
  13127.  row and column position. For example, to move the cursor back one position
  13128.  (but not to the left of column 0), you can use the following code:
  13129.  
  13130.    USHORT usRow, usCol ;
  13131.  
  13132.         ....
  13133.  
  13134.    VioGetCurPos (&usRow, &usCol, hvps) ;
  13135.  
  13136.    if (usCol > 0)
  13137.         VioSetCurPos (usRow, --usCol, hvps) ;
  13138.  
  13139.  The only other VIO function that changes the position of the cursor is
  13140.  VioWrtTTY.
  13141.  
  13142.  By default, the cursor is an underline similar to the hardware cursor
  13143.  supported by most video boards in character modes. Unlike the hardware
  13144.  cursor, the AVIO cursor does not blink. You can change the size of the
  13145.  cursor or make it invisible using VioSetCurType. This function requires a
  13146.  structure of type VIOCURSORINFO defined in BSESUB.H:
  13147.  
  13148.    typedef struct _VIOCURSORINFO
  13149.         {
  13150.         USHORT yStart ;
  13151.         USHORT cEnd ;
  13152.         USHORT cx ;
  13153.         USHORT attr ;
  13154.         }
  13155.         VIOCURSORINFO ;
  13156.  
  13157.  By convention, a structure of type VIOCURSORINFO begins with the prefix
  13158.  vioci. You define such a variable like this:
  13159.  
  13160.    VIOCURSORINFO vioci ;
  13161.  
  13162.  To obtain the current VIOCURSORINFO settings, use
  13163.  
  13164.    VioGetCurType (&vioci, hvps) ;
  13165.  
  13166.  To set new values, use
  13167.  
  13168.    VioSetCurType (&vioci, hvps) ;
  13169.  
  13170.  You don't need to make a call to VioShowBuf or VioShowPS after you change
  13171.  the cursor size.
  13172.  
  13173.  The height of the cursor is specified by the yStart and cEnd fields, which
  13174.  are in units of pixels measured from the top of a character, starting with
  13175.  0. (To determine the height of a character in pixels, you use the
  13176.  VioGetDeviceCellSize function described under "Cell Size Functions.") The
  13177.  cEnd field must be greater than or equal to the yStart field. If the two
  13178.  fields are equal, the cursor is 1 pixel high.
  13179.  
  13180.  For example, suppose that characters are 10 pixels high. To create a box
  13181.  cursor the full height of the character, you can use the following code:
  13182.  
  13183.    VioGetCurType (&vioci, hvps) ;
  13184.  
  13185.    vioci.yStart = 0 ;
  13186.    vioci.cEnd = 9 ;
  13187.  
  13188.    VioSetCurType (&vioci, hvps) ;
  13189.  
  13190.  The Presentation Manager displays the cursor in reverse video, so the
  13191.  character is still visible.
  13192.  
  13193.  The width of the cursor is specified by cx. Because the cursor can be only 1
  13194.  character wide, you can only set this field equal to 1 (which indicates the
  13195.  cursor is 1 character wide) or 0 (which indicates a default width, again 1
  13196.  character wide).
  13197.  
  13198.  You can set the attr field to -1 to hide the cursor, or to any other value
  13199.  to display the cursor. The function on the next page hides the cursor.
  13200.  
  13201.    VOID HideCursor (HVPS hvps)
  13202.         {
  13203.         VIOCURSORINFO vioci ;
  13204.  
  13205.         VioGetCurType (&vioci, hvps) ;
  13206.         vioci.attr = -1 ;
  13207.         VioSetCurType (&vioci, hvps) ;
  13208.         }
  13209.  
  13210.  If a program does not use VioWrtTTY for displaying output (as in AVIO1), the
  13211.  cursor will be hidden anyway. This function shows the cursor again:
  13212.  
  13213.    VOID ShowCursor (HVPS hvps)
  13214.         {
  13215.         VIOCURSORINFO vioci ;
  13216.  
  13217.         VioGetCurType (&vioci, hvps) ;
  13218.         vioci.attr = 0 ;
  13219.         VioSetCurType (&vioci, hvps) ;
  13220.         }
  13221.  
  13222.  As you'll learn in Chapter 8, it's a good practice to hide a cursor when a
  13223.  program loses the input focus and to display the cursor when the program
  13224.  gains the input focus. The presence of the cursor indicates to the user when
  13225.  the program can accept keyboard input.
  13226.  
  13227.  Output Functions
  13228.  
  13229.  Seven VIO functions let you display text or attributes on the virtual
  13230.  display buffer. The first six of these functions require parameters to
  13231.  specify the row and column position where the text or attributes begin.
  13232.  
  13233.  You've already seen how the VioWrtCharStr function works in AVIO1.C:
  13234.  
  13235.    VioWrtCharStr (&chCharString, cbCharString, usRow, usCol, hvps)
  13236.  
  13237.  This function requires a pointer to a character string and the length of the
  13238.  string. The function does not affect the attributes already set for the
  13239.  cells.
  13240.  
  13241.  You can also write a string of cells. Each cell is a character followed by 1
  13242.  attribute byte or 3 attribute bytes:
  13243.  
  13244.    VioWrtCellStr (&chCellString, cbCellString, usRow, usCol, hvps)
  13245.  
  13246.  The cbCellString parameter is the number of bytes in the string: an even
  13247.  number for single attribute bytes and a multiple of 4 for extended
  13248.  attributes. In practice, this function is rather awkward to use. It makes
  13249.  most sense when used in conjunction with VioReadCellStr (described under
  13250.  "Input Functions").
  13251.  
  13252.  You can also write a character string that uses the same attribute for all
  13253.  characters in the string:
  13254.  
  13255.    VioWrtCharStrAtt (&chCharString, cbCharString, usRow, usCol, &bAttr, hvps)
  13256.  
  13257.  The &bAttr parameter is a pointer to 1 byte or 3 bytes. For example, suppose
  13258.  you want to display the text "Hello" at row 0 and column 1. You're using 3
  13259.  attribute bytes and you want underlined yellow letters on a blue background.
  13260.  The statement is:
  13261.  
  13262.    VioWrtCharStrAtt ("Hello", 5, 0, 1, "\x1E\x80\0", hvps) ;
  13263.  
  13264.  The following three functions display the same character, the same
  13265.  attribute, or the same cell repeated a specified number of times:
  13266.  
  13267.    VioWrtNChar (&chChar, cbRepetition, usRow, usCol, hvps)
  13268.    VioWrtNAttr (&bAttr, cbRepetition, usRow, usCol, hvps)
  13269.    VioWrtNCell (&bCell, cbRepetition, usRow, usCol, hvps)
  13270.  
  13271.  The &chChar parameter in VioWrtNChar points to a 1-byte string. The &bAttr
  13272.  parameter in VioWrtNAttr points to a 1-byte or 3-byte string. The &bCell
  13273.  parameter in VioWrtNCell points to a 2-byte or 4-byte string.
  13274.  
  13275.  For example, suppose cxDisplay is the width of the AVIO presentation space
  13276.  in characters, cyDisplay is the height in rows, and you're using the 1
  13277.  attribute byte option. You can set the entire AVIO virtual display buffer to
  13278.  yellow asterisks on a blue background by calling:
  13279.  
  13280.    VioWrtNCell ("*\x1E", cxDisplay * cyDisplay, 0, 0, hvps) ;
  13281.  
  13282.  The VioWrtNAttr function is useful for highlighting (or un-highlighting) a
  13283.  text string already in the virtual display buffer.
  13284.  
  13285.  None of these six functions changes the cursor position: Output that
  13286.  continues past the end of a line wraps to the next line; output that exceeds
  13287.  the length of the virtual display buffer is ignored. Control codes, such as
  13288.  tabs and carriage returns, are displayed as characters──they are not
  13289.  interpreted.
  13290.  
  13291.  The seventh, and highest-level, text-output function is VioWrtTTY:
  13292.  
  13293.    VioWrtTTY (&chCharString, cbCharString, hvps) ;
  13294.  
  13295.  The first parameter is a pointer to a text string, and the second parameter
  13296.  is the number of characters in the string. VioWrtTTY displays the text
  13297.  string starting at the current cursor position and leaves the cursor at the
  13298.  next position following the end of the text.
  13299.  
  13300.  As with the first six output functions, text displayed by VioWrtTTY wraps at
  13301.  the end of a line. However, VioWrtTTY doesn't ignore text that exceeds the
  13302.  length of the virtual display buffer. Instead, it scrolls the contents of
  13303.  the virtual display buffer up one line to continue displaying the text on
  13304.  the last line. The first line in the virtual display buffer is lost.
  13305.  
  13306.  VioWrtTTY also recognizes and interprets five control codes. These
  13307.  characters are not displayed:
  13308.  
  13309.    Control Character   Action
  13310.    Bell ('\a')         Beeps the speaker.
  13311.    Backspace ('\b')    Moves the cursor back one position without deleting,
  13312.                        but not beyond column 0.
  13313.    Tab ('\t')          Inserts spaces to the next column position that is a
  13314.                        multiple of 8.
  13315.    Linefeed ('\n')     Moves the cursor down one line (and possibly scrolls
  13316.                        the contents of the virtual display buffer).
  13317.    Carriage return     Moves the cursor to the beginning of the current line.
  13318.    ('\r')
  13319.  
  13320.  VioWrtTTY also recognizes ANSI control sequences if ANSI processing is
  13321.  enabled.
  13322.  
  13323.  ANSI Control Sequences
  13324.  
  13325.  The AVIO version of VioWrtTTY supports a small subset (Figure 7-11) of
  13326.  control sequences defined by American National Standards document
  13327.  X3.64-1979, "Additional Controls for Use with American National Standard
  13328.  Code for Information Interchange [ASCII]." These are commonly referred to as
  13329.  "ANSI control sequences." You can mix ANSI control sequences with text
  13330.  strings that you pass to VioWrtTTY. These control sequences let you set the
  13331.  cursor position and foreground and background colors.
  13332.  
  13333.  ───────────────────────────────────────────────────────────────────────────
  13334.  NOTE:
  13335.     Some ANSI control sequences recognized by the character-mode VIO
  13336.     interface are not recognized by AVIO.
  13337.  ───────────────────────────────────────────────────────────────────────────
  13338.  
  13339.  Figure 7-11.  ANSI control sequences recognized by the AVIO version of
  13340.                VioWrtTTY.
  13341.  
  13342.       ANSI Sequence       Action
  13343.  
  13344.       "\33[2J"            Erases screen with current attribute and sets the
  13345.                           cursor to the upper-left corner.
  13346.       "\33[K"             Erases line starting from current cursor position
  13347.                           using current attribute.
  13348.       "\33[row;colH"      Sets cursor to row and col.
  13349.       "\33[row;colf"      Sets cursor to row and col.
  13350.       "\33[nA"            Moves cursor up n rows.
  13351.       "\33[nB"            Moves cursor down n rows.
  13352.       "\33[nC"            Moves cursor forward n columns.
  13353.       "\33[nD"            Moves cursor backward n columns.
  13354.       "\33[s"             Saves current cursor position.
  13355.       "\33[u"             Restores cursor position from saved value.
  13356.       "\33[x;...;xm"      Sets attributes.
  13357.  
  13358.  
  13359.  Keep the following points in mind as you work with the control sequences:
  13360.  
  13361.    ■  The first character of the control sequence is always an ASCII escape
  13362.       character (\33 in octal or \x1B in hexadecimal). The second character
  13363.       is always a left bracket.
  13364.  
  13365.    ■  The two control sequences that set a cursor position use row and column
  13366.       values starting at 1 rather than 0. (Thus the upper-left corner of the
  13367.       virtual display buffer is row 1 and column 1.)
  13368.  
  13369.    ■  The four functions that move the cursor n positions do not move the
  13370.       cursor past the boundaries of the virtual display buffer.
  13371.  
  13372.    ■  The functions that save and restore the cursor position cannot be
  13373.       nested.
  13374.  
  13375.  The last control sequence shown in Figure 7-11 sets colors that are used
  13376.  for text displayed after the control sequence and for erasing the virtual
  13377.  display buffer. x can be one or more of the numbers shown in Figure 7-12.
  13378.  
  13379.  Figure 7-12.  Values used in setting attributes using ANSI.
  13380.  
  13381.       Color             Foreground        Background
  13382.  
  13383.       Black             30                40
  13384.       Red               31                41
  13385.       Green             32                42
  13386.       Yellow            33                43
  13387.       Blue              34                44
  13388.       Magenta           35                45
  13389.       Cyan              36                46
  13390.       White             37                47
  13391.  
  13392.       Other Attributes                    Value
  13393.  
  13394.       Reset attributes to default:        0
  13395.       Bold (light) foreground:            1
  13396.       Reverse video:                      7
  13397.       Hide characters:                    8
  13398.  
  13399.  
  13400.  For example, if you want to use yellow (light brown) characters on a blue
  13401.  background, you can set that attribute and clear the virtual display buffer
  13402.  using the following statement:
  13403.  
  13404.    VioWrtTTY ("\33[33;44;1m\33[2J", 14, hvps) ;
  13405.  
  13406.  By default, ANSI processing is enabled. You can disable ANSI processing by
  13407.  calling the following function:
  13408.  
  13409.    VioSetAnsi (ANSI_OFF, hvps) ;
  13410.  
  13411.  You can enable it again by calling
  13412.  
  13413.    VioSetAnsi (ANSI_ON, hvps) ;
  13414.  
  13415.  The only reasons for disabling ANSI processing are to display the character
  13416.  associated with the ASCII escape code or to implement another terminal
  13417.  emulation protocol that uses the escape code. You can determine whether ANSI
  13418.  processing is enabled or disabled by calling
  13419.  
  13420.    VioGetAnsi (&usAnsiState, hvps) ;
  13421.  
  13422.  On return from the function, the variable usAnsiState is set to either
  13423.  ANSI_ON or ANSI_OFF.
  13424.  
  13425.  Using C Output Functions
  13426.  
  13427.  You may be aware that three "console" output functions included in the
  13428.  Microsoft C libraries use VioWrtTTY to write to the screen. These functions
  13429.  are putch (display a character), cputs (display a zero-terminated string),
  13430.  and cprintf (display a formatted string similar to printf). The cputs and
  13431.  cprintf functions call putch for each character they display. The functions
  13432.  are declared in the CONIO.H header file.
  13433.  
  13434.  At first it may seem possible to use these functions to write on an AVIO
  13435.  presentation space. But that's not so. The simple reason: The putch function
  13436.  included in the C libraries calls VioWrtTTY with the last parameter set to 0
  13437.  rather than to hvps. The zero parameter is the value that OS/2
  13438.  character-mode applications use.
  13439.  
  13440.  However, in your program you can define a new version of putch that uses
  13441.  hvps as the last parameter to VioWrtTTY. To do this you need to define hvps
  13442.  as a global variable:
  13443.  
  13444.    #include <conio.h>  // declaration of putch
  13445.  
  13446.         ....
  13447.  
  13448.    HVPS hvps ;         // handle to AVIO PS must be global!
  13449.  
  13450.         ....
  13451.  
  13452.    int putch (char ch)
  13453.         {
  13454.         return VioWrtTTY (&ch, 1, hvps) ;
  13455.         }
  13456.  
  13457.  If you do this, then cputs and cprintf both write output to the AVIO virtual
  13458.  display buffer. Unlike puts, cputs does not append a newline character to
  13459.  the string, so you'll want to use "\r\n" to move the cursor to the next
  13460.  line. Unlike printf, cprintf does not translate the C newline character (\n)
  13461.  into a carriage return and linefeed sequence, so you'll probably want to use
  13462.  \r\n to go to the beginning of the next line rather than just \n.
  13463.  
  13464.  But keep in mind that cputs and cprintf will be somewhat slow because they
  13465.  call VioWrtTTY for each character individually. It is much more efficient to
  13466.  call VioWrtTTY for a whole string of characters. If you want to use cputs,
  13467.  you'll get better performance by defining a new version of the function:
  13468.  
  13469.    #include <conio.h>  // declaration of cputs
  13470.    #include <string.h> // declaration of strlen
  13471.  
  13472.         ....
  13473.  
  13474.    HVPS hvps ;         // handle to AVIO PS must be global!
  13475.  
  13476.         ....
  13477.  
  13478.    int cputs (char *psz)
  13479.         {
  13480.         return VioWrtTTY (psz, strlen (psz), hvps) ;
  13481.         }
  13482.  
  13483.  A more efficient version of cprintf is slightly more difficult but certainly
  13484.  not impossible. It uses the vsprintf function, which is similar to sprintf
  13485.  but with an important difference: The vsprintf function accepts a pointer to
  13486.  the items to be formatted rather than accepting the items themselves. This
  13487.  allows arguments passed on a stack to be used by the vsprintf function. The
  13488.  va_start, va_arg, and va_end macros defined in STDARG.H help in creating
  13489.  this new cprintf function:
  13490.  
  13491.    #include <conio.h>       // declaration of cprintf
  13492.    #include <stdio.h>       // declaration of vsprintf
  13493.    #include <stdarg.h>      // declaration of va_start, etc.
  13494.  
  13495.         ....
  13496.  
  13497.    #define MAXLENGTH 80     // maximum length of formatted string
  13498.  
  13499.         ....
  13500.  
  13501.    HVPS hvps ;              // handle to AVIO PS must be global!
  13502.  
  13503.         ....
  13504.  
  13505.    int cprintf (char *szFormat, ...)
  13506.         {
  13507.         CHAR    chBuffer [MAXLENGTH] ;
  13508.         SHORT   sLength ;
  13509.         va_list pArguments ;
  13510.  
  13511.         va_start (pArguments, szFormat) ;
  13512.         sLength = vsprintf (chBuffer, szFormat, pArguments) ;
  13513.  
  13514.         VioWrtTTY (chBuffer, sLength, hvps) ;
  13515.  
  13516.         va_end (pArguments) ;
  13517.         return sLength ;
  13518.         }
  13519.  
  13520.  Input Functions
  13521.  
  13522.  Two VIO functions can read from the virtual display buffer:
  13523.  
  13524.    VioReadCellStr (&chCellString, &cb, usRow, usCol, hvps) ;
  13525.    VioReadCharStr (&chCharString, &cb, usRow, usCol, hvps) ;
  13526.  
  13527.  In both cases, the first parameter is a pointer to a buffer that receives
  13528.  the cell string or character string. The second parameter is a pointer to a
  13529.  USHORT variable that you set to the number of bytes you want to read. On
  13530.  return from the function, the cb parameter indicates the number of bytes
  13531.  actually read. This could be fewer than the number you specified if the
  13532.  count exceeds the size of the virtual display buffer.
  13533.  
  13534.  The VioReadCellStr function can be used to save an area of the virtual
  13535.  display. You restore the area with VioWrtCellStr. (You can use
  13536.  VioReadCharStr and VioWrtCharStr instead if you don't need to save and
  13537.  restore the attributes.) You can also use VioReadCellStr in conjunction with
  13538.  VioWrtCellStr to alter part of the virtual display. For example, suppose you
  13539.  use the 3 attribute option and you want to alter a 20-character string by
  13540.  setting reverse video. Here's the code:
  13541.  
  13542.    BYTE   bCellBuffer [20][4] ;
  13543.    USHORT i, cb ;
  13544.  
  13545.         ....
  13546.  
  13547.    cb = 20 * 4 ;
  13548.  
  13549.    VioReadCellStr (bCellBuffer, &cb, usRow, usCol, hvps) ;
  13550.  
  13551.    for (i = 0 ; i < 20 ; i++)
  13552.             bCellBuffer [i][2] |= '\x40' ;
  13553.  
  13554.    VioWrtCellStr (bCellBuffer, cb, usRow, usCol, hvps) ;
  13555.  
  13556.  Scrolling Functions
  13557.  
  13558.  Four VIO functions scroll a rectangular area of the virtual display buffer
  13559.  up, down, left, and right. You specify upper-left and lower-right character
  13560.  positions, the number of lines to scroll, and the cell that is used to fill
  13561.  the area left uncovered by the scroll.
  13562.  
  13563.  The most common scrolling function is the one that scrolls a rectangular
  13564.  area up:
  13565.  
  13566.    VioScrollUp (usTopRow, usLeftCol, usBottomRow, usRightCol,
  13567.                 cbLines, &bCell, hvps) ;
  13568.  
  13569.  The scrolled area is a rectangle that includes the usTopRow and usBottomRow
  13570.  rows and the usLeftCol and usRightCol columns.
  13571.  
  13572.    ■  usTopRow must be less than or equal to usBottomRow.
  13573.    ■  usLeftCol must be less than or equal to usRightCol.
  13574.  
  13575.  The cbLines parameter indicates the number of lines to scroll. (The function
  13576.  doesn't do anything if cbLines is 0.) The area at the bottom of the
  13577.  rectangle left uncovered by the scroll is filled with the cell specified as
  13578.  the pointer to bCell.
  13579.  
  13580.  If your values for usBottomRow, usRightCol, or cbLines exceed the maximum,
  13581.  they are set to the maximum. Thus, it is customary to use -1 (equivalent to
  13582.  65,535 when interpreted as an unsigned value) when you want to use the
  13583.  maximums. For example, to scroll the entire contents of the virtual buffer
  13584.  up one line, use
  13585.  
  13586.    VioScrollUp (0, 0, -1, -1, 1, " \7", hvps) ;
  13587.  
  13588.  The top line of the virtual buffer is lost. The last line is filled with
  13589.  blanks with the attribute 7 (the default light gray on black color). If you
  13590.  were using 3 attributes, the cell string would be \7\0\0.
  13591.  
  13592.  To blank the entire virtual buffer, set the cbLines parameter to a maxi mum
  13593.  value:
  13594.  
  13595.    VioScrollUp (0, 0, -1, -1, -1, " \x1E", hvps) ;
  13596.  
  13597.  The function to scroll a rectangular area down has the same syntax:
  13598.  
  13599.    VioScrollDn (usTopRow, usLeftCol, usBottomRow, usRightCol, cbLines,
  13600.                 &bCell, hvps) ;
  13601.  
  13602.  The lines on the bottom are lost. The lines on top are filled with bCell.
  13603.  
  13604.  The following two functions scroll an area left or right:
  13605.  
  13606.    VioScrollLf (usTopRow, usLeftCol, usBottomRow, usRightCol, cbColumns, &bCel
  13607.    VioScrollRt (usTopRow, usLeftCol, usBottomRow, usRightCol, cbColumns, &bCel
  13608.  
  13609.  The syntax is the same as that used in the other two scrolling functions
  13610.  except that the fifth parameter is the number of columns rather than the
  13611.  number of lines. These functions will have no effect if the cbColumns
  13612.  parameter is set to 0.
  13613.  
  13614.  Keep in mind that these scrolling functions move the contents of the virtual
  13615.  display buffer and result in one or more lines or columns being lost from
  13616.  the buffer. You may prefer to use VioSetOrg to move the virtual display
  13617.  buffer relative to the window rather than to move cells within the buffer.
  13618.  
  13619.  Origin Functions
  13620.  
  13621.  Normally the upper-left corner of the virtual display buffer (row 0 and
  13622.  column 0) is displayed in the upper-left corner of the window. You can
  13623.  change that through use of the VioSetOrg function:
  13624.  
  13625.    VioSetOrg (sRow, sCol, hvps) ;
  13626.  
  13627.  After this call, the sRow and sCol position in the virtual display buffer is
  13628.  displayed in the upper-left corner of the window. The SYSVALS4 program
  13629.  shown later in this chapter uses this function to shift data within the
  13630.  window. You can obtain the current origin by calling
  13631.  
  13632.    VioGetOrg (&sRow, &sCol, hvps) ;
  13633.  
  13634.  Cell Size Functions
  13635.  
  13636.  When using GPI functions for character output, you obtain the dimensions of
  13637.  a character by calling GpiQueryFontMetrics. With AVIO, you use
  13638.  VioGetDeviceCellSize:
  13639.  
  13640.    VioGetDeviceCellSize (&cyChar, &cxChar, hvps) ;
  13641.  
  13642.  On return from the function, cyChar and cxChar will be set to the height and
  13643.  width, in pixels, of the character cell.
  13644.  
  13645.  You can also set a new cell size by calling
  13646.  
  13647.    VioSetDeviceCellSize (cyChar, cxChar, hvps) ;
  13648.  
  13649.  However, this function is more limited than you may initially assume. The
  13650.  AVIO interface supports (at most) two cell sizes: a large cell size and a
  13651.  small cell size. Initially, the cell size is large, roughly approximating
  13652.  the size of characters in a character-mode session. When a program's window
  13653.  is maximized, the client window can display at least 25 rows of 80
  13654.  characters using this cell size.
  13655.  
  13656.  You can switch to the smaller cell size by calling VioSetDeviceCellSize with
  13657.  very low values:
  13658.  
  13659.    VioSetDeviceCellSize (1, 1, hvps) ;
  13660.  
  13661.  You then use VioGetDeviceCellSize to determine what size the character cells
  13662.  really are.
  13663.  
  13664.  You can also obtain this information by using DevQueryCaps. The
  13665.  CAPS_CHAR_HEIGHT and CAPS_CHAR_WIDTH parameters report the large cell size,
  13666.  and the CAPS_SMALL_CHAR_HEIGHT and CAPS _SMALL_CHAR_WIDTH parameters report
  13667.  the small cell size. (You may want to run the DEVCAPS program from Chapter
  13668.  5 to see what these sizes are for your particular display adapter.) If
  13669.  DevQueryCaps returns 0 for the CAPS_SMALL_CHAR_HEIGHT and
  13670.  CAPS_SMALL_CHAR_WIDTH parameters, then a small cell size is not available.
  13671.  
  13672.  When the Presentation Manager runs an OS/2 1.0 program in a window, it
  13673.  includes the "Small Font" option on the program's system menu. This lets the
  13674.  user select a small cell size if one is available. After switching to the
  13675.  small cell size, the system menu allows switching back with the "Large Font"
  13676.  option. You may want to provide a similar facility for your Presentation
  13677.  Manager programs that use AVIO.
  13678.  
  13679.  Virtual Display Buffer Functions
  13680.  
  13681.  As you saw in AVIO2, a program can obtain a pointer to the virtual display
  13682.  buffer and write to it directly. The VioGetBuf function returns a pointer to
  13683.  the buffer as a ULONG value. The size of the buffer in bytes is returned in
  13684.  usVideoLength:
  13685.  
  13686.    VioGetBuf (&ulVideoBuffer, &usVideoLength, hvps) ;
  13687.  
  13688.  When writing directly to the buffer, you need to update the window from the
  13689.  buffer by calling VioShowBuf or VioShowPS. The various VioWrt functions
  13690.  write text and attributes to the buffer and (if possible) to the window.
  13691.  
  13692.  Two functions update the window from the virtual display buffer. The first
  13693.  is a VIO function included in OS/2 1.0:
  13694.  
  13695.    VioShowBuf (usByteOffset, usLength, hvps) ;
  13696.  
  13697.  The usByteOffset parameter is an offset in bytes from the beginning of the
  13698.  virtual display buffer. The usLength parameter indicates the number of bytes
  13699.  to update.
  13700.  
  13701.  The second function is an AVIO function:
  13702.  
  13703.    VioShowPS (sHeight, sWidth, sCellOffset, hvps) ;
  13704.  
  13705.  This updates a rectangle of cells that is sHeight characters high and sWidth
  13706.  characters wide with the upper-left corner at the sCellOffset character.
  13707.  Note that this function always updates complete cells. The sCellOffset is
  13708.  the number of character cells from the beginning of the buffer; the
  13709.  usByteOffset parameter in VioShowBuf specifies a starting position in bytes.
  13710.  
  13711.  For example, suppose your virtual display buffer is 10 characters wide and
  13712.  has 1 attribute byte. Each row has 10 cells (20 bytes). You want to update
  13713.  the second and third rows. The VioShowBuf call is
  13714.  
  13715.    VioShowBuf (20, 40, hvps) ;
  13716.  
  13717.  The VioShowPS call is
  13718.  
  13719.    VioShowPS (2, 10, 10, hvps) ;
  13720.  
  13721.  You should call one of these two functions during the WM_PAINT message to
  13722.  update the invalid area of the window.
  13723.  
  13724.  Miscellaneous VIO Functions
  13725.  
  13726.  Three other OS/2 1.0 VIO functions are supported under the Presentation
  13727.  Manager AVIO interface.
  13728.  
  13729.  The VioGetConfig uses a structure of type VIOCONFIGINFO:
  13730.  
  13731.    struct _VIOCONFIGINFO
  13732.         {
  13733.         USHORT cb ;
  13734.         USHORT adapter ;
  13735.         USHORT display ;
  13736.         ULONG cbMemory ;
  13737.         }
  13738.         VIOCONFIGINFO ;
  13739.  
  13740.  You define a structure of type VIOCONFIGINFO, set the cb field to the size
  13741.  of the structure, and pass a pointer to the structure to VioGetConfig:
  13742.  
  13743.    VIOCONFIGINFO vioin ;
  13744.  
  13745.         ....
  13746.  
  13747.    vioin.cb = sizeof VIOCONFIGINFO ;
  13748.  
  13749.    VioGetConfig (0, &vioin, hvps) ;
  13750.  
  13751.  The first parameter of VioGetConfig must be set to 0. On return from the
  13752.  function, the adapter and display fields contain codes that identify the
  13753.  video adapter and display. The cbMemory field is not available under the
  13754.  AVIO interface. It's unlikely that this function provides meaningful
  13755.  information to a Presentation Manager program.
  13756.  
  13757.  The Return of SYSVALS
  13758.  
  13759.  After enduring several versions of the SYSVALS program in Chapter 4, you
  13760.  may have thought we were done with it. No such luck. SYSVALS is back! The
  13761.  AVIO version is called SYSVALS4 and is shown in Figure 7-13.
  13762.  
  13763.  Figure 7-13.  The SYSVALS4 program.
  13764.  
  13765.    The SYSVALS4 File
  13766.  
  13767.    #--------------------
  13768.    # SYSVALS4 make file
  13769.    #--------------------
  13770.  
  13771.    sysvals4.obj : sysvals4.c sysvals.h
  13772.         cl -c -G2sw -W3 sysvals4.c
  13773.  
  13774.    sysvals4.exe : sysvals4.obj sysvals4.def
  13775.         link sysvals4, /align:16, NUL, os2, sysvals4
  13776.  
  13777.    The SYSVALS4.C File
  13778.  
  13779.    /*--------------------------------------------------------
  13780.       SYSVALS4.C -- System Values Display Program using AVIO
  13781.      --------------------------------------------------------*/
  13782.  
  13783.    #define INCL_WIN
  13784.    #define INCL_GPI
  13785.    #define INCL_VIO
  13786.    #define INCL_AVIO
  13787.    #include <os2.h>
  13788.    #include <stdio.h>
  13789.    #include <stdlib.h>
  13790.    #include "sysvals.h"
  13791.  
  13792.    #define MAXWIDTH 60
  13793.  
  13794.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  13795.  
  13796.    HAB  hab ;
  13797.  
  13798.    int main (void)
  13799.         {
  13800.         static CHAR  szClientClass [] = "SysVals4" ;
  13801.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
  13802.                                     FCF_SIZEBORDER    | FCF_MINMAX   |
  13803.                                     FCF_SHELLPOSITION | FCF_TASKLIST |
  13804.                                     FCF_VERTSCROLL    | FCF_HORZSCROLL ;
  13805.         HMQ          hmq ;
  13806.         HWND         hwndFrame, hwndClient ;
  13807.         QMSG         qmsg ;
  13808.  
  13809.         hab = WinInitialize (0) ;
  13810.         hmq = WinCreateMsgQueue (hab, 0) ;
  13811.  
  13812.         WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;
  13813.  
  13814.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  13815.                                         &flFrameFlags, szClientClass, NULL,
  13816.                                         0L, NULL, 0, &hwndClient) ;
  13817.  
  13818.         WinSendMsg (hwndFrame, WM_SETICON,
  13819.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  13820.                     NULL) ;
  13821.  
  13822.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  13823.              WinDispatchMsg (hab, &qmsg) ;
  13824.  
  13825.         WinDestroyWindow (hwndFrame) ;
  13826.         WinDestroyMsgQueue (hmq) ;
  13827.         WinTerminate (hab) ;
  13828.         return 0 ;
  13829.         }
  13830.  
  13831.    BYTE RgbToVioColor (COLOR clrRgb)
  13832.         {
  13833.         BYTE bIrgb ;
  13834.         RGB  rgb ;
  13835.  
  13836.         rgb = MAKETYPE (clrRgb, RGB) ;
  13837.  
  13838.         if (rgb.bBlue  >= 0x80) bIrgb |= '\x01' ;
  13839.         if (rgb.bGreen >= 0x80) bIrgb |= '\x02' ;
  13840.         if (rgb.bRed   >= 0x80) bIrgb |= '\x04' ;
  13841.  
  13842.         if (rgb.bBlue >= 0xC0 || rgb.bGreen >= 0xC0 || rgb.bRed >= 0xC0)
  13843.              bIrgb |= 8 ;
  13844.  
  13845.         if (bIrgb == 0 && rgb.bBlue >= 0x40 && rgb.bGreen >= 0x40 &&
  13846.                           rgb.bRed  >= 0x40)
  13847.              bIrgb = 8 ;
  13848.  
  13849.         return bIrgb ;
  13850.         }
  13851.  
  13852.    BYTE ConstructDefaultAttribute (VOID)
  13853.         {
  13854.         return RgbToVioColor (
  13855.                     WinQuerySysColor (HWND_DESKTOP, SYSCLR_WINDOW, 0L)) << 4 |
  13856.                RgbToVioColor (
  13857.                     WinQuerySysColor (HWND_DESKTOP, SYSCLR_WINDOWTEXT, 0L)) ;
  13858.         }
  13859.  
  13860.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  13861.         {
  13862.         static BYTE   bBlankCell [2] = " " ;
  13863.         static HVPS   hvps ;
  13864.         static HWND   hwndHscroll, hwndVscroll ;
  13865.         static HPS    hps ;
  13866.         static SHORT  sHscrollPos, sVscrollPos,
  13867.                       cxChar, cyChar, cxClient, cyClient ;
  13868.         CHAR          szBuffer [80] ;
  13869.         HDC           hdc ;
  13870.         SIZEL         sizl ;
  13871.         USHORT        usRow ;
  13872.  
  13873.         switch (msg)
  13874.              {
  13875.              case WM_CREATE:
  13876.                   hdc = WinOpenWindowDC (hwnd) ;
  13877.  
  13878.                   sizl.cx = sizl.cy = 0 ;
  13879.                   hps = GpiCreatePS (hab, hdc, &sizl, PU_PELS    | GPIF_DEFAUL
  13880.                                                       GPIT_MICRO | GPIA_ASSOC)
  13881.  
  13882.                   VioCreatePS (&hvps, NUMLINES, MAXWIDTH, 0, 1, NULL) ;
  13883.                   VioAssociate (hdc, hvps) ;
  13884.                   VioGetDeviceCellSize (&cyChar, &cxChar, hvps) ;
  13885.  
  13886.                   bBlankCell[1] = ConstructDefaultAttribute () ;
  13887.                   VioScrollUp (0, 0, -1, -1, -1, bBlankCell, hvps) ;
  13888.  
  13889.                   for (usRow = 0 ; usRow < NUMLINES ; usRow++)
  13890.                        VioWrtCharStr (szBuffer,
  13891.                                       sprintf (szBuffer, "%-20s%-35s%5ld",
  13892.                                                sysvals[usRow].szIdentifier,
  13893.                                                sysvals[usRow].szDescription,
  13894.                                                WinQuerySysValue (HWND_DESKTOP,
  13895.                                                sysvals[usRow].sIndex)),
  13896.                                       usRow, 0, hvps) ;
  13897.  
  13898.                   hwndHscroll = WinWindowFromID (
  13899.                                       WinQueryWindow (hwnd, QW_PARENT, FALSE),
  13900.                                       FID_HORZSCROLL) ;
  13901.  
  13902.                   WinSendMsg (hwndHscroll, SBM_SETSCROLLBAR,
  13903.                                            MPFROM2SHORT (sHscrollPos, 0),
  13904.                                            MPFROM2SHORT (0, MAXWIDTH - 1)) ;
  13905.  
  13906.                   hwndVscroll = WinWindowFromID (
  13907.                                       WinQueryWindow (hwnd, QW_PARENT, FALSE),
  13908.                                       FID_VERTSCROLL) ;
  13909.  
  13910.                   WinSendMsg (hwndVscroll, SBM_SETSCROLLBAR,
  13911.                                            MPFROM2SHORT (sVscrollPos, 0),
  13912.                                            MPFROM2SHORT (0, NUMLINES - 1)) ;
  13913.                   return 0 ;
  13914.  
  13915.              case WM_SIZE:
  13916.                   cxClient = SHORT1FROMMP (mp2) ;
  13917.                   cyClient = SHORT2FROMMP (mp2) ;
  13918.  
  13919.                   WinDefAVioWindowProc (hwnd, msg, mp1, mp2) ;
  13920.                   return 0 ;
  13921.  
  13922.              case WM_HSCROLL:
  13923.                   switch (SHORT2FROMMP (mp2))
  13924.                        {
  13925.                        case SB_LINELEFT:
  13926.                             sHscrollPos -= 1 ;
  13927.                             break ;
  13928.  
  13929.                        case SB_LINERIGHT:
  13930.                             sHscrollPos += 1 ;
  13931.                             break ;
  13932.  
  13933.                        case SB_PAGELEFT:
  13934.                             sHscrollPos -= 8 ;
  13935.                             break ;
  13936.  
  13937.                        case SB_PAGERIGHT:
  13938.                             sHscrollPos += 8 ;
  13939.                             break ;
  13940.  
  13941.                        case SB_SLIDERPOSITION:
  13942.                             sHscrollPos = SHORT1FROMMP (mp2) ;
  13943.                             break ;
  13944.                        }
  13945.                   sHscrollPos = max (0, min (sHscrollPos, MAXWIDTH - 1)) ;
  13946.  
  13947.                   if (sHscrollPos != SHORT1FROMMR (WinSendMsg (hwndHscroll,
  13948.                                           SBM_QUERYPOS, NULL, NULL)))
  13949.                        {
  13950.                        VioSetOrg (sVscrollPos, sHscrollPos, hvps) ;
  13951.  
  13952.                        WinSendMsg (hwndHscroll, SBM_SETPOS,
  13953.                                    MPFROM2SHORT (sHscrollPos, 0), NULL) ;
  13954.                        }
  13955.                   return 0 ;
  13956.  
  13957.              case WM_VSCROLL:
  13958.                   switch (SHORT2FROMMP (mp2))
  13959.                        {
  13960.                        case SB_LINEUP:
  13961.                             sVscrollPos -= 1 ;
  13962.                             break ;
  13963.  
  13964.                        case SB_LINEDOWN:
  13965.                             sVscrollPos += 1 ;
  13966.                             break ;
  13967.  
  13968.                        case SB_PAGEUP:
  13969.                             sVscrollPos -= cyClient / cyChar ;
  13970.                             break ;
  13971.  
  13972.                        case SB_PAGEDOWN:
  13973.                             sVscrollPos += cyClient / cyChar ;
  13974.                             break ;
  13975.  
  13976.                        case SB_SLIDERPOSITION:
  13977.                             sVscrollPos = SHORT1FROMMP (mp2) ;
  13978.                             break ;
  13979.                        }
  13980.                   sVscrollPos = max (0, min (sVscrollPos, NUMLINES - 1)) ;
  13981.  
  13982.                   if (sVscrollPos != SHORT1FROMMR (WinSendMsg (hwndVscroll,
  13983.                                           SBM_QUERYPOS, NULL, NULL)))
  13984.                        {
  13985.                        VioSetOrg (sVscrollPos, sHscrollPos, hvps) ;
  13986.  
  13987.                        WinSendMsg (hwndVscroll, SBM_SETPOS,
  13988.                                    MPFROM2SHORT (sVscrollPos, 0), NULL) ;
  13989.                        }
  13990.                   return 0 ;
  13991.  
  13992.              case WM_CHAR:
  13993.                   switch (CHARMSG(&msg)->vkey)
  13994.                        {
  13995.                        case VK_LEFT:
  13996.                        case VK_RIGHT:
  13997.                             return WinSendMsg (hwndHscroll, msg, mp1, mp2) ;
  13998.                        case VK_UP:
  13999.                        case VK_DOWN:
  14000.                        case VK_PAGEUP:
  14001.                        case VK_PAGEDOWN:
  14002.                             return WinSendMsg (hwndVscroll, msg, mp1, mp2) ;
  14003.                        }
  14004.                   break ;
  14005.  
  14006.              case WM_PAINT:
  14007.                   WinBeginPaint (hwnd, hps, NULL) ;
  14008.                   GpiErase (hps) ;
  14009.  
  14010.                   VioShowBuf (0, MAXWIDTH * NUMLINES * 2, hvps) ;
  14011.  
  14012.                   WinEndPaint (hps) ;
  14013.                   return 0 ;
  14014.  
  14015.              case WM_DESTROY:
  14016.                   VioAssociate (NULL, hvps) ;
  14017.                   VioDestroyPS (hvps) ;
  14018.                   GpiDestroyPS (hps) ;
  14019.                   return 0 ;
  14020.              }
  14021.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  14022.         }
  14023.  
  14024.    The SYSVALS4.DEF File
  14025.  
  14026.    ;------------------------------------
  14027.    ; SYSVALS4.DEF module definition file
  14028.    ;------------------------------------
  14029.  
  14030.    NAME           SYSVALS4  WINDOWAPI
  14031.  
  14032.    DESCRIPTION    'System Values Display using AVIO (C) Charles Petzold, 1988'
  14033.    PROTMODE
  14034.    HEAPSIZE       1024
  14035.    STACKSIZE      8192
  14036.    EXPORTS        ClientWndProc
  14037.  
  14038.  
  14039.  To compile this program you'll also need the SYSVALS.H header file from
  14040.  Chapter 4.
  14041.  
  14042.  SYSVALS4 calculates an attribute that provides the same window background
  14043.  and window text colors that the user selected in the Presentation Manager
  14044.  Control Panel program. This requires a little work. The
  14045.  ConstructDefaultAttribute function first calls WinQuerySysColor to obtain
  14046.  the window background and text colors. These are returned in ULONG values in
  14047.  which red, green, and blue bytes (each ranging from 0 to 255) are encoded.
  14048.  The RgbToVioColor function converts these RGB values into the 4-bit IRGB
  14049.  encoding. These two 4-bit values are combined into 1 byte in the
  14050.  ConstructDefaultAttribute routine. ClientWndProc then uses this attribute
  14051.  with the VioScrollUp function to clear the AVIO virtual display buffer. The
  14052.  text is written to the AVIO buffer using VioWrtCharStr.
  14053.  
  14054.  Processing the WM_HSCROLL and WM_VSCROLL messages is fairly straightforward.
  14055.  SYSVALS4 simply uses the current scroll-bar positions to set the new origin.
  14056.  The Presentation Manager then updates the window from this new origin
  14057.  without any additional code.
  14058.  
  14059.  WM_PAINT processing is similarly straightforward. SYSVALS4 simply calls
  14060.  VioShowBuf to update the entire window.
  14061.  
  14062.  
  14063.  SECTION THREE     GETTING INPUT
  14064.  
  14065.  Chapter 8  Tapping Into the Keyboard
  14066.  ───────────────────────────────────────────────────────────────────────────
  14067.  
  14068.  
  14069.  Despite the sophisticated user interface of the Presentation Manager
  14070.  (including the mouse, menus, and dialog boxes), the keyboard is still the
  14071.  primary means of user input in most applications. Even if you write a
  14072.  Presentation Manager program that makes extensive use of the mouse, you
  14073.  should also include a keyboard interface that duplicates the mouse
  14074.  functions. As you probably know, many users still simply refuse to allow a
  14075.  mouse anywhere near their desk. The Presentation Manager doesn't require a
  14076.  mouse, so your programs shouldn't either.
  14077.  
  14078.  A Presentation Manager program receives keyboard input in the form of
  14079.  messages. You process these keyboard messages in the same way you process
  14080.  other messages. In fact, because user input is closer to one's intuitive
  14081.  concept of a message, working with these keyboard messages should be easier
  14082.  in some ways than handling other types of messages.
  14083.  
  14084.  As you've seen in previous programs, the Presentation Manager itself handles
  14085.  a large part of keyboard processing. The keystrokes involved in choosing an
  14086.  item from the system menu are handled outside the client window procedure,
  14087.  as are the keyboard accelerators that duplicate system menu options. Many
  14088.  child window controls (discussed in Chapter 11) have their own keyboard
  14089.  interface. The Presentation Manager also takes care of keyboard processing
  14090.  in a program's menu (Chapter 13) and dialog boxes (Chapter 14). But this
  14091.  isn't to say that keyboard handling is easy. The Presentation Manager
  14092.  delivers a lot of information to your program with the keyboard message. You
  14093.  need to recognize what is important and what you can safely ignore.
  14094.  
  14095.  Keyboard processing becomes more complex if you want to ensure that your
  14096.  programs can survive the transition to a system with a foreign keyboard. By
  14097.  "foreign keyboard" I mean any type of keyboard that is different from the
  14098.  one you have on your desk. This includes a variety of European keyboards,
  14099.  keyboards used in Far Eastern countries that generate double-byte character
  14100.  codes, and even keyboards for non-PC computers that might someday run
  14101.  versions of the Presentation Manager. The Presentation Manager has a
  14102.  device-independent keyboard interface, but──as is the case with other
  14103.  device-independent interfaces──you have to help. You can even design your
  14104.  Presentation Manager programs so that they can be recompiled for a system
  14105.  with a keyboard that generates codes in the EBCDIC (Extended Binary Coded
  14106.  Decimal Interchange Code) character set used on IBM mainframes and
  14107.  minicomputers.
  14108.  
  14109.  
  14110.  The Keyboard and Codes
  14111.  
  14112.  A keyboard always generates numeric codes of various sorts. Within a
  14113.  program, you make an implicit assumption about how these codes relate to the
  14114.  keys that generate them.
  14115.  
  14116.  You can think of the keyboard in one of two ways──as a collection of
  14117.  distinct physical keys or as a means of generating character codes. When you
  14118.  treat the keyboard as a collection of keys, any code generated by the
  14119.  keyboard must identify the key and indicate whether the key is being pressed
  14120.  or released. When you treat the keyboard as a character input device, a code
  14121.  generated by a particular keystroke identifies a unique character in a
  14122.  character set. For a U.S. keyboard on the PC, this character set is ASCII.
  14123.  For a European keyboard, however, it is an extended ASCII character set that
  14124.  includes accented letters and other symbols not in the standard ASCII
  14125.  character set. For a keyboard on an IBM mainframe, it is the EBCDIC
  14126.  character set. If you obtain a character code from the keyboard and echo it
  14127.  to the display, it should look the same as the character printed on the top
  14128.  of the key. That is, the visual appearance of the character on the screen
  14129.  shouldn't surprise the user. This requires that the keyboard driver and
  14130.  display driver are working with the same character set or "codepage."
  14131.  
  14132.  Because many of the keys on the keyboard aren't associated with character
  14133.  codes, you must usually treat the keyboard as both a collection of keys and
  14134.  a character generator. You can divide the keyboard into four general groups
  14135.  of keys:
  14136.  
  14137.    ■  Toggle keys──The Caps Lock, Num Lock, and Scroll Lock keys and
  14138.       possibly the Insert key. Pressing the key turns the state of the key
  14139.       on; pressing it again turns the state off.
  14140.  
  14141.    ■  Shift keys──The Shift, Ctrl, and Alt keys. The shift keys affect the
  14142.       interpretation of other keys.
  14143.  
  14144.    ■  Noncharacter keys──The function keys, the cursor movement keys, Pause,
  14145.       Escape, Delete, and possibly the Insert key. These keys aren't
  14146.       associated with characters but instead often direct a program to carry
  14147.       out a particular action.
  14148.  
  14149.    ■  Character keys──The letter, number, and symbol keys, the Spacebar, the
  14150.       Tab key, Backspace, and Enter. (The Tab, Backspace, and Enter keys can
  14151.       also be treated as noncharacter keys.)
  14152.  
  14153.  Often a single physical key can generate different character codes depending
  14154.  on the shift keys. For example, the A key generates a lowercase a or an
  14155.  uppercase A depending on the Shift key. Sometimes two different physical
  14156.  keys (such as the two Enter keys on an IBM enhanced keyboard) can generate
  14157.  the same character code.
  14158.  
  14159.  The Presentation Manager handles the keyboard somewhat differently from
  14160.  other PC keyboard interfaces with which you may be more familiar. To put
  14161.  this into perspective, let's examine these other keyboard interfaces.
  14162.  
  14163.  Pre-OS/2 Keyboard Processing
  14164.  
  14165.  The hardware of the keyboard on a PC generates a "hardware scan code." This
  14166.  is an 8-bit code that identifies the physical key and indicates whether the
  14167.  key is being pressed or released. Hardware scan codes are usually numbered
  14168.  sequentially across the rows of keys.
  14169.  
  14170.  In the world of real mode and MS-DOS, the PC BIOS processes each keystroke
  14171.  through its Interrupt 09H handler. For hardware scan codes corresponding to
  14172.  shift keys and toggle keys, the Interrupt 09H handler stores the current
  14173.  state of the key. For character keys, the hardware scan code is converted
  14174.  into an ASCII character code based on the state of the shift and toggle keys
  14175.  and is stored in a small buffer. For noncharacter keys, the hardware scan
  14176.  code is converted into an "extended keyboard code" and also stored in the
  14177.  buffer.
  14178.  
  14179.  A program running under MS-DOS can obtain keystrokes from the buffer through
  14180.  various MS-DOS function calls or the BIOS Interrupt 16H. For character keys,
  14181.  Interrupt 16H returns the ASCII character code and the hardware scan code.
  14182.  For noncharacter keys, the extended keyboard code is returned, and the ASCII
  14183.  code is set to 0.
  14184.  
  14185.  In summary, the PC BIOS works with three types of codes:
  14186.  
  14187.    ■  Hardware scan code──Generated from keyboard hardware.
  14188.  
  14189.    ■  Extended keyboard code──Identifies noncharacter keys in combination
  14190.       with the Shift, Ctrl, or Alt key.
  14191.  
  14192.    ■  ASCII character code──Identifies character keys based on the Shift,
  14193.       Ctrl, or Caps Lock key.
  14194.  
  14195.  The OS/2 Kernel and the Keyboard
  14196.  
  14197.  When OS/2 is running, the keyboard is handled by the OS/2 kernel rather than
  14198.  the PC BIOS. However, the OS/2 keyboard interface closely mimics the
  14199.  operation of the BIOS. A program running under the OS/2 kernel obtains
  14200.  keyboard input by calling the DosRead, KbdCharIn, or KbdStringIn function.
  14201.  The KbdCharIn function is the most general and is similar to Interrupt 16H.
  14202.  The keyboard information from KbdCharIn is stored in a structure of type
  14203.  KBDKEYINFO.
  14204.  
  14205.  Two fields of KBDKEYINFO identify the key. The chChar field contains an
  14206.  ASCII character code. If this field is 0, the chScan field contains an
  14207.  extended keyboard code. (Despite the name of this field, and the OS/2 kernel
  14208.  documentation, this field does not contain a hardware scan code.) The
  14209.  fsState field is a 16-bit integer with flags that identify the current state
  14210.  of the shift and toggle keys.
  14211.  
  14212.  Enter the Presentation Manager
  14213.  
  14214.  Rather than use DosRead or the Kbd functions to obtain keyboard input, a
  14215.  Presentation Manager program receives keyboard information in the form of
  14216.  messages. These messages contain more information about keyboard activity
  14217.  than is available from the OS/2 KbdCharIn function. When a key is pressed or
  14218.  released, the Presentation Manager decodes the key and stores the
  14219.  information about the keystroke in a system message queue. This keyboard
  14220.  message is later routed to the message queue of the window with the input
  14221.  focus (a concept discussed later in this chapter) and then retrieved by the
  14222.  program.
  14223.  
  14224.  Where the Presentation Manager differs from other PC keyboard interfaces is
  14225.  mostly in the treatment of the noncharacter keys. The Presentation Manager
  14226.  doesn't use the extended keyboard codes because they are too dependent on
  14227.  the specific hardware of the PC and would make little sense for versions of
  14228.  the Presentation Manager adapted for different hardware. Instead, an attempt
  14229.  has been made to virtualize the codes for noncharacter keys. A fourth type
  14230.  of keyboard code has been introduced──the "virtual key code." Like the
  14231.  hardware scan code, the virtual key code generally identifies a physical key
  14232.  and isn't dependent on a particular shift state. (There are a couple of
  14233.  exceptions.)
  14234.  
  14235.  Armed with this historical perspective, let's examine the Presentation
  14236.  Manager keyboard message.
  14237.  
  14238.  
  14239.  The WM_CHAR Message
  14240.  
  14241.  In most cases a Presentation Manager program can obtain all the information
  14242.  it needs about keyboard input by processing the WM_CHAR message in the
  14243.  client window procedure. The information encoded in the mp1 and mp2
  14244.  parameters is shown in Figure 8-1.
  14245.  
  14246.  Figure 8-1.  The WM_CHAR mp1 and mp2 parameters.
  14247.  
  14248.              ┌──┬──┬───┬──┬──┬──┬──┬───┬──┬──┬──┬──┬───┬──┬──┐
  14249.          mp1 │31│30│...│25│24│23│22│...│17│16│15│14│...│ 1│ 0│
  14250.              └──┴──┴───┴──┴──┴──┴──┴───┴──┴──┴──┴──┴───┴──┴──┘
  14251.              └───────┬───────╨───────┬───────╨───────┬───────┘
  14252.                  8-bit           8-bit             16-bit
  14253.                  hardware        repeat            flags
  14254.                  scan code       count             (fs
  14255.                  (scancode)      (cRepeat)
  14256.  
  14257.              ┌──┬──┬───┬──┬──┬──┬──┬───┬──┬──┐
  14258.          mp2 │31│30│...│17│16│15│14│...│ 1│ 0│
  14259.              └──┴──┴───┴──┴──┴──┴──┴───┴──┴──┘
  14260.              └───────┬───────╨───────┬───────┘
  14261.                   16-bit          16-bit
  14262.                   vitual          character
  14263.                   key code        code
  14264.                   (vkey)          (chr)
  14265.  
  14266.  
  14267.  You can use a variety of macros defined in PMWIN.H──such as SHORT1FROMMP
  14268.  and CHAR3FROMMP──to extract each of these fields. Or you can use a macro
  14269.  called CHARMSG designed specifically for processing WM_CHAR messages. You
  14270.  use CHARMSG like this:
  14271.  
  14272.    CHARMSG (&msg) -> identifier
  14273.  
  14274.  where identifier is one of the identifiers in parentheses shown in Figure
  14275.  8-1. This macro references the mp1 and mp2 parameters to the window
  14276.  procedure from the stack.
  14277.  
  14278.  ───────────────────────────────────────────────────────────────────────────
  14279.  NOTE:
  14280.     If you want to use CHARMSG in a subroutine called from the window
  14281.     procedure, you must pass msg, mp1, and mp2 to the subroutine (in that
  14282.     order), and the subroutine must be defined as PASCAL.
  14283.  ───────────────────────────────────────────────────────────────────────────
  14284.  
  14285.  The lower 16 bits of mp1 contain a series of flags that further describe the
  14286.  keyboard message. The individual flags can be extracted using identifiers
  14287.  beginning with the letters KC defined in the PMWIN.H header file. These
  14288.  flags are shown in Figure 8-2.
  14289.  
  14290.  Figure 8-2.  Flags defined in the WM_CHAR mp1 parameter.
  14291.  
  14292. ╓┌────────────────────────────────────────────────────────────┌──────────────╖
  14293.  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐            Meaning if Bit
  14294.  │15│14│13│12│11│10│ 9│ 8│ 7│ 6│ 5│ 4│ 3│ 2│ 1│ 0│            is Set
  14295.  └──┴──┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┘
  14296.          │  │  │  │  │  │  │  │  │  │  │  │  │  └KC_CHAR      Valid ASCII
  14297.          │  │  │  │  │  │  │  │  │  │  │  │  │                character code
  14298.          │  │  │  │  │  │  │  │  │  │  │  │  │
  14299.          │  │  │  │  │  │  │  │  │  │  │  │  └─KC_VIRTUALKEY  Valid virtual
  14300.          │  │  │  │  │  │  │  │  │  │  │  │                   key code
  14301.          │  │  │  │  │  │  │  │  │  │  │  │
  14302.          │  │  │  │  │  │  │  │  │  │  │  └────KC_SCANCODE    Valid hardware
  14303.  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐            Meaning if Bit
  14304.  │15│14│13│12│11│10│ 9│ 8│ 7│ 6│ 5│ 4│ 3│ 2│ 1│ 0│            is Set
  14305.  └──┴──┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┘
  14306.         │  │  │  │  │  │  │  │  │  │  │  └────KC_SCANCODE    Valid hardware
  14307.          │  │  │  │  │  │  │  │  │  │  │                      scan mode
  14308.          │  │  │  │  │  │  │  │  │  │  │
  14309.          │  │  │  │  │  │  │  │  │  │  └───────KC_SHIFT       Shift key is
  14310.          │  │  │  │  │  │  │  │  │  │                         down
  14311.          │  │  │  │  │  │  │  │  │  │
  14312.          │  │  │  │  │  │  │  │  │  └──────────KC_CTRL        Ctrl key is
  14313.          │  │  │  │  │  │  │  │  │                            down
  14314.          │  │  │  │  │  │  │  │  │
  14315.          │  │  │  │  │  │  │  │  └─────────────KC_ALT         Alt key is down
  14316.          │  │  │  │  │  │  │  │
  14317.          │  │  │  │  │  │  │  └────────────────KC_KEYUP       Key is being
  14318.          │  │  │  │  │  │  │                                  released
  14319.          │  │  │  │  │  │  │
  14320.          │  │  │  │  │  │  └───────────────────KC_PREVDOWN    Key was
  14321.          │  │  │  │  │  │                                     previously down
  14322.          │  │  │  │  │  │
  14323.          │  │  │  │  │  └──────────────────────KC_LONEKEY     The only key
  14324.  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐            Meaning if Bit
  14325.  │15│14│13│12│11│10│ 9│ 8│ 7│ 6│ 5│ 4│ 3│ 2│ 1│ 0│            is Set
  14326.  └──┴──┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┘
  14327.         │  │  │  │  │  └──────────────────────KC_LONEKEY     The only key
  14328.          │  │  │  │  │                                        pressed is
  14329.          │  │  │  │  │                                        being released
  14330.          │  │  │  │  │
  14331.          │  │  │  │  └─────────────────────────KC_DEADKEY     Key is a dead
  14332.          │  │  │  │                                           key
  14333.          │  │  │  │
  14334.          │  │  │  └────────────────────────────KC_COMPOSITE   Key is a
  14335.          │  │  │                                              composite using
  14336.          │  │  │                                              a diacritic
  14337.          │  │  │
  14338.          │  │  └───────────────────────────────KC_INVALIDCOMP Key is an
  14339.          │  │                                                 invalid
  14340.          │  │                                                 composite
  14341.          │  │
  14342.          │  └──────────────────────────────────KC_TOGGLE      Identifies
  14343.          │                                                    toggle state
  14344.          │
  14345.  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐            Meaning if Bit
  14346.  │15│14│13│12│11│10│ 9│ 8│ 7│ 6│ 5│ 4│ 3│ 2│ 1│ 0│            is Set
  14347.  └──┴──┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┴─┬┘
  14348.         │
  14349.          └─────────────────────────────────────KC_INVALIDCHAR Key is not an
  14350.                                                               invalid
  14351.  
  14352.  
  14353.  You can test these flags with one of two expressions:
  14354.  
  14355.    SHORT1FROMMP (mp1) & KC_SHIFT
  14356.  
  14357.  or
  14358.  
  14359.    CHARMSG (&msg) -> fs &  KC_SHIFT
  14360.  
  14361.  Both expressions return a nonzero value if the KC_SHIFT flag is set (meaning
  14362.  the Shift key is down) or 0 if the flag is 0 (meaning the Shift key is up).
  14363.  
  14364.  Looking at the Keys
  14365.  
  14366.  As I discuss the various codes and flags in the mp1 and mp2 parameters, you
  14367.  may find it helpful to observe what the Presentation Manager actually gives
  14368.  your program in the WM_CHAR message when you press a particular key. To do
  14369.  this, you can use the KEYLOOK program, shown in Figure 8-3.
  14370.  
  14371.  Figure 8-3.  The KEYLOOK program.
  14372.  
  14373.    The KEYLOOK File
  14374.  
  14375.    #-------------------
  14376.    # KEYLOOK make file
  14377.    #-------------------
  14378.  
  14379.    keylook.obj : keylook.c
  14380.         cl -c -G2sw -W3 keylook.c
  14381.  
  14382.    easyfont.obj : easyfont.c
  14383.         cl -c -G2sw -W3 easyfont.c
  14384.  
  14385.    keylook.exe : keylook.obj easyfont.obj keylook.def
  14386.         link keylook easyfont, /align:16, NUL, os2, keylook
  14387.  
  14388.    The KEYLOOK.C File
  14389.  
  14390.    /*----------------------------------------
  14391.       KEYLOOK.C -- Displays WM_CHAR Messages
  14392.      ----------------------------------------*/
  14393.  
  14394.    #define INCL_WIN
  14395.    #define INCL_GPI
  14396.    #include <os2.h>
  14397.    #include <stdio.h>
  14398.    #include "easyfont.h"
  14399.  
  14400.    #define LCID_FIXEDFONT 1L
  14401.    #define MAX_KEYS       100
  14402.  
  14403.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  14404.  
  14405.    CHAR szClientClass [] = "KeyLook" ;
  14406.    HAB  hab ;
  14407.  
  14408.    int main (void)
  14409.         {
  14410.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  14411.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  14412.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  14413.         HMQ          hmq ;
  14414.         HWND         hwndFrame, hwndClient ;
  14415.         QMSG         qmsg ;
  14416.  
  14417.         hab = WinInitialize (0) ;
  14418.         hmq = WinCreateMsgQueue (hab, 0) ;
  14419.  
  14420.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  14421.  
  14422.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  14423.                                         &flFrameFlags, szClientClass, NULL,
  14424.                                         0L, NULL, 0, &hwndClient) ;
  14425.         if (hwndFrame != NULL)
  14426.              {
  14427.              WinSendMsg (hwndFrame, WM_SETICON,
  14428.                          WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE
  14429.                          NULL) ;
  14430.  
  14431.              while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  14432.                   WinDispatchMsg (hab, &qmsg) ;
  14433.  
  14434.              WinDestroyWindow (hwndFrame) ;
  14435.              }
  14436.         WinDestroyMsgQueue (hmq) ;
  14437.         WinTerminate (hab) ;
  14438.         return 0 ;
  14439.         }
  14440.  
  14441.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  14442.         {
  14443.         static CHAR   szHeader [] = "Scan  Rept  IN TG IC CM DK LK PD KU"
  14444.                                     " AL CT SH SC VK CH  Virt  Char" ;
  14445.         static CHAR   szUndrLn [] = "----  ----  -- -- -- -- -- -- -- --"
  14446.                                     " -- -- -- -- -- --  ----  ----" ;
  14447.         static CHAR   szFormat [] = "%4X %4dx  %2d %2d %2d %2d %2d %2d %2d %2d
  14448.                                     " %2d %2d %2d %2d %2d %2d  %4X  %4X  %c" ;
  14449.  
  14450.         static SHORT  cxChar, cyChar, cyDesc, cxClient, cyClient, sNextKey ;
  14451.         static struct {
  14452.                       MPARAM mp1 ;
  14453.                       MPARAM mp2 ;
  14454.                       BOOL   fValid ;
  14455.                       }
  14456.                       key [MAX_KEYS] ;
  14457.         CHAR          szBuffer [80] ;
  14458.         FONTMETRICS   fm ;
  14459.         HPS           hps ;
  14460.         POINTL        ptl ;
  14461.         RECTL         rcl, rclInvalid ;
  14462.         SHORT         sKey, sIndex, sFlag ;
  14463.  
  14464.         switch (msg)
  14465.              {
  14466.              case WM_CREATE:
  14467.                   hps = WinGetPS (hwnd) ;
  14468.                   EzfQueryFonts (hps) ;
  14469.  
  14470.                   if (!EzfCreateLogFont (hps, LCID_FIXEDFONT, FONTFACE_COUR,
  14471.                                                               FONTSIZE_10, 0))
  14472.                        {
  14473.                        WinReleasePS (hps) ;
  14474.  
  14475.                        WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
  14476.                             "Cannot find a fixed-pitch font.  Load the Courier
  14477.                             "fonts from the Control Panel and try again.",
  14478.                             szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;
  14479.  
  14480.                        return 1 ;
  14481.                        }
  14482.  
  14483.                   GpiSetCharSet (hps, LCID_FIXEDFONT) ;
  14484.  
  14485.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  14486.                   cxChar = (SHORT) fm.lAveCharWidth ;
  14487.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  14488.                   cyDesc = (SHORT) fm.lMaxDescender ;
  14489.  
  14490.                   GpiSetCharSet (hps, LCID_DEFAULT) ;
  14491.                   GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
  14492.                   WinReleasePS (hps) ;
  14493.                   return 0 ;
  14494.  
  14495.              case WM_SIZE:
  14496.                   cxClient = SHORT1FROMMP (mp2) ;
  14497.                   cyClient = SHORT2FROMMP (mp2) ;
  14498.                   return 0 ;
  14499.  
  14500.              case WM_CHAR:
  14501.                   key [sNextKey].mp1 = mp1 ;
  14502.                   key [sNextKey].mp2 = mp2 ;
  14503.                   key [sNextKey].fValid = TRUE ;
  14504.  
  14505.                   sNextKey = (sNextKey + 1) % MAX_KEYS ;
  14506.  
  14507.                   WinSetRect (hwnd, &rcl,
  14508.                               0, 2 * cyChar, cxClient, cyClient - 2 * cyChar)
  14509.  
  14510.                   WinScrollWindow (hwnd, 0, cyChar, &rcl, &rcl, NULL, NULL,
  14511.                                                     SW_INVALIDATERGN) ;
  14512.                   WinUpdateWindow (hwnd) ;
  14513.                   return 0 ;
  14514.  
  14515.              case WM_PAINT:
  14516.                   hps = WinBeginPaint (hwnd, NULL, &rclInvalid) ;
  14517.                   GpiErase (hps) ;
  14518.                   EzfCreateLogFont (hps, LCID_FIXEDFONT, FONTFACE_COUR,
  14519.                                                          FONTSIZE_10, 0) ;
  14520.                   GpiSetCharSet (hps, LCID_FIXEDFONT) ;
  14521.  
  14522.                   ptl.x = cxChar ;
  14523.                   ptl.y = cyDesc ;
  14524.                   GpiCharStringAt (hps, &ptl, sizeof szHeader - 1L, szHeader)
  14525.  
  14526.                   ptl.y += cyChar ;
  14527.                   GpiCharStringAt (hps, &ptl, sizeof szUndrLn - 1L, szUndrLn)
  14528.  
  14529.                   for (sKey = 0 ; sKey < MAX_KEYS ; sKey++)
  14530.                        {
  14531.                        ptl.y += cyChar ;
  14532.  
  14533.                        sIndex = (sNextKey - sKey - 1 + MAX_KEYS) % MAX_KEYS ;
  14534.  
  14535.                        if (ptl.y > rclInvalid.yTop ||
  14536.                                  ptl.y > cyClient - 2 * cyChar ||
  14537.                                       !key [sIndex].fValid)
  14538.                             break ;
  14539.  
  14540.                        mp1 = key [sIndex].mp1 ;
  14541.                        mp2 = key [sIndex].mp2 ;
  14542.  
  14543.                        sFlag = CHARMSG(&msg)->fs ;
  14544.  
  14545.                        GpiCharStringAt (hps, &ptl,
  14546.                             (LONG) sprintf (szBuffer, szFormat,
  14547.                                       CHARMSG(&msg)->scancode,
  14548.                                       CHARMSG(&msg)->cRepeat,
  14549.                                       sFlag & KC_INVALIDCHAR ? 1 : 0,
  14550.                                       sFlag & KC_TOGGLE      ? 1 : 0,
  14551.  
  14552.                                       sFlag & KC_INVALIDCOMP ? 1 : 0,
  14553.                                       sFlag & KC_COMPOSITE   ? 1 : 0,
  14554.                                       sFlag & KC_DEADKEY     ? 1 : 0,
  14555.                                       sFlag & KC_LONEKEY     ? 1 : 0,
  14556.                                       sFlag & KC_PREVDOWN    ? 1 : 0,
  14557.                                       sFlag & KC_KEYUP       ? 1 : 0,
  14558.                                       sFlag & KC_ALT         ? 1 : 0,
  14559.                                       sFlag & KC_CTRL        ? 1 : 0,
  14560.                                       sFlag & KC_SHIFT       ? 1 : 0,
  14561.                                       sFlag & KC_SCANCODE    ? 1 : 0,
  14562.                                       sFlag & KC_VIRTUALKEY  ? 1 : 0,
  14563.                                       sFlag & KC_CHAR        ? 1 : 0,
  14564.                                       CHARMSG(&msg)->vkey,
  14565.                                       CHARMSG(&msg)->chr,
  14566.                                       sFlag & KC_CHAR ? CHARMSG(&msg)->chr : '
  14567.                                  szBuffer) ;
  14568.                        }
  14569.                   ptl.y = cyClient - cyChar + cyDesc ;
  14570.                   GpiCharStringAt (hps, &ptl, sizeof szHeader - 1L, szHeader)
  14571.  
  14572.                   ptl.y -= cyChar ;
  14573.                   GpiCharStringAt (hps, &ptl, sizeof szUndrLn - 1L, szUndrLn)
  14574.  
  14575.                   GpiSetCharSet (hps, LCID_DEFAULT) ;
  14576.                   GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
  14577.                   WinEndPaint (hps) ;
  14578.                   return 0 ;
  14579.              }
  14580.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  14581.         }
  14582.  
  14583.    The KEYLOOK.DEF File
  14584.  
  14585.    ;------------------------------------
  14586.    ; KEYLOOK.DEF module definition file
  14587.    ;------------------------------------
  14588.  
  14589.    NAME           KEYLOOK   WINDOWAPI
  14590.  
  14591.    DESCRIPTION    'Key Look Program (C) Charles Petzold, 1988'
  14592.    PROTMODE
  14593.    HEAPSIZE       1024
  14594.    STACKSIZE      8192
  14595.    EXPORTS        ClientWndProc
  14596.  
  14597.  
  14598.  Compiling KEYLOOK also requires the EASYFONT.C and EASYFONT.H files from
  14599.  Chapter 5. KEYLOOK uses a fixed-pitch Courier font to ease the display of
  14600.  WM_CHAR information in the window. If a Courier font is not available,
  14601.  KEYLOOK displays a message box and returns 1 from the WM_CREATE message.
  14602.  This aborts creation of the program's window and causes the
  14603.  WinCreateStdWindow cell in main to return NULL. Figure 8-4 shows KEYLOOK
  14604.  running under the Presentation Manager after the word Keyboard has been
  14605.  typed.
  14606.  
  14607.  KEYLOOK displays the contents of each WM_CHAR message it receives, starting
  14608.  with the high fields of mp1 and working down. The heading in KEYLOOK uses
  14609.  abbreviations to identify this information:
  14610.  
  14611.     Heading     Description
  14612.     Scan        Hardware scan code in hexadecimal
  14613.     Rept        Repeat count in decimal
  14614.     IN          KC_INVALIDCHAR flag (0 or 1)
  14615.     TG          KC_TOGGLE flag
  14616.     IC          KC_INVALIDCOMP flag
  14617.     CM          KC_COMPOSITE flag
  14618.     DK          KC_DEADKEY flag
  14619.     LK          KC_LONEKEY flag
  14620.     PD          KC_PREVDOWN flag
  14621.  
  14622.     Heading     Description
  14623.     KU          KC_KEYUP flag
  14624.     AL          KC_ALT flag
  14625.     CT          KC_CTRL flag
  14626.     SH          KC_SHIFT flag
  14627.     SC          KC_SCANCODE flag
  14628.     VK          KC_VIRTUALKEY flag
  14629.     CH          KC_CHAR flag
  14630.     Virt        Virtual key code in hexadecimal
  14631.     Char        ASCII character code in hexadecimal
  14632.  
  14633.  Following the hexadecimal representation of the character code, KEYLOOK also
  14634.  displays the character itself.
  14635.  
  14636.  The Three Keyboard Codes
  14637.  
  14638.  The mp1 and mp2 parameters accompanying the WM_CHAR message contain three
  14639.  codes that identify the key or character. These are the hardware scan code,
  14640.  the virtual key code, and the character code.
  14641.  
  14642.  Hardware Scan Code
  14643.  If the KC_SCANCODE bit is set, the upper 8 bits of mp1 contain a valid
  14644.  hardware scan code. The KC_SCANCODE bit is set for all WM_CHAR messages you
  14645.  receive in a window procedure. The hardware scan code can be extracted with
  14646.  the expression
  14647.  
  14648.    CHAR4FROMMP (mp1)
  14649.  
  14650.  or
  14651.  
  14652.    CHARMSG (& msg) -> scancode
  14653.  
  14654.  Presentation Manager programs usually ignore this code. The hardware scan
  14655.  codes will be quite different for non-PCs running a future version of the
  14656.  Presentation Manager. Using this code will guarantee that you'll have to
  14657.  modify your programs to run on these machines. You're on your own here.
  14658.  There is no support in the header files for using these codes; you'll have
  14659.  to do some research on scan code values in the PC technical reference
  14660.  manuals. (But that won't prevent me from using the scan code in the ORGAN
  14661.  program shown at the end of this chapter.)
  14662.  
  14663.  Virtual Key Code
  14664.  If the KC_VIRTUALKEY bit is set, the upper 16 bits of mp2 contain a valid
  14665.  virtual key code. The virtual key code can be extracted with the expression
  14666.  
  14667.    SHORT2FROMMP (mp2)
  14668.  
  14669.  or
  14670.  
  14671.    CHARMSG (&msg) -> vkey
  14672.  
  14673.  If the KC_VIRTUALKEY flag is 0, the upper 16 bits of mp2 are also set to 0.
  14674.  The virtual key code is used for keys that don't generate characters. The
  14675.  code generally identifies the key being pressed or released independent of
  14676.  the shift states. (The major exception is for the keyboard number pad.) I'll
  14677.  discuss the virtual key codes in detail later in this chapter.
  14678.  
  14679.  Character Code
  14680.  If the KC_CHAR flag is set, the lower 16 bits of mp2 contain a valid
  14681.  character code. A character code is present in the WM_CHAR message only when
  14682.  the key is being pressed (that is, the KC_KEYUP flag is 0) and the key
  14683.  generates a character. While processing the WM_CHAR message, you can obtain
  14684.  the character code with the expression
  14685.  
  14686.    SHORT1FROMMP (mp2)
  14687.  
  14688.  or
  14689.  
  14690.    CHARMSG (&msg) -> chr
  14691.  
  14692.  The character code reflects the state of the Shift key at the time the key
  14693.  is pressed. On IBM PCs and compatibles, this character code is usually from
  14694.  the ASCII character set. For European keyboards, however, the character code
  14695.  could have a value of 128 or above for letters and symbols not present in
  14696.  the ASCII character set. You can better code your programs for easy
  14697.  adaptation to other implementations of the Presentation Manager by making no
  14698.  assumptions about the character set.
  14699.  
  14700.  If you run KEYLOOK and type a letter key in combination with the Ctrl key,
  14701.  you'll notice that the Presentation Manager sets neither KC_VIRTUALKEY nor
  14702.  KC_CHAR flag to 1 for the letter key.
  14703.  
  14704.  This presents a problem for programs that need to recognize Ctrl-letter key
  14705.  combinations, such as modem communications programs that need to recognize
  14706.  Ctrl-letter combinations typed at the keyboard in order to send the ASCII
  14707.  control code to the communications port. For example, when the user types
  14708.  Ctrl-S to suspend incoming data, the program needs to convert that key
  14709.  combination to an ASCII code of 0x13 (known as XOFF).
  14710.  
  14711.  These Ctrl-letter keys have to be handled as a special case: If the
  14712.  KC_VIRTUALKEY, KC_CHAR, and KC_KEYUP flags are set to 0, the KC_CTRL flag is
  14713.  set to 1, and if the character code is not 0, the character code is the
  14714.  ASCII code of the letter being typed. You can convert that character code to
  14715.  an ASCII control code with this expression:
  14716.  
  14717.    (CHARMSG(&msg)->chr) & 0x1F
  14718.  
  14719.  Processing Virtual Keys and Characters
  14720.  
  14721.  The processing of a WM_CHAR message in a window procedure is often divided
  14722.  into two parts: processing character keys and processing non character
  14723.  keys. For a few keys (Enter, Backspace, Space, and Tab) both the
  14724.  KC_VIRTUALKEY and KC_CHAR flags are set. You can process these keys as
  14725.  virtual keys or character keys. The number pad generates both virtual codes
  14726.  and character codes if Num Lock is on. I'll discuss the number pad shortly.
  14727.  
  14728.  The easiest approach is to examine the KC_CHAR flag first and process the
  14729.  character keys if the flag is set. You can then check the KC_VIRTUALKEY and
  14730.  process noncharacter keys. The code looks something like this:
  14731.  
  14732.    case WM_CHAR:
  14733.              [other program lines]
  14734.         if (CHARMSG (&msg) -> fs & KC_CHAR)
  14735.              {
  14736.              switch (CHARMSG (&msg) -> chr)
  14737.                   {
  14738.                        [process character keys]
  14739.                   }
  14740.              }
  14741.  
  14742.         else if (CHARMSG (&msg) -> fs & KC_VIRTUALKEY)
  14743.              {
  14744.              switch (CHARMSG (&msg) -> vkey)
  14745.                   {
  14746.                        [process noncharacter keys]
  14747.                   }
  14748.              }
  14749.              [other program lines]
  14750.         return 1;
  14751.  
  14752.  The two sections marked "process character keys" and "process noncharacter
  14753.  keys" each have a series of case statements for processing particular keys
  14754.  or characters.
  14755.  
  14756.  A Closer Look at Virtual Key Codes
  14757.  When processing a virtual key, you use a switch and case construction to
  14758.  compare the virtual key code to identifiers beginning with VK defined in the
  14759.  PMWIN.H header file. The virtual key codes defined in PMWIN.H fall into
  14760.  several categories. Here is the group of identifiers for the function keys:
  14761.  
  14762.     VK_F1         VK_F7         VK_F13        VK_F19
  14763.     VK_F2         VK_F8         VK_F14        VK_F20
  14764.     VK_F3         VK_F9         VK_F15        VK_F21
  14765.     VK_F4         VK_F10        VK_F16        VK_F22
  14766.     VK_F5         VK_F11        VK_F17        VK_F23
  14767.     VK_F6         VK_F12        VK_F18        VK_F24
  14768.  
  14769.  A machine that runs the Presentation Manager is required to have only the
  14770.  first ten function keys on the keyboard. The others are optional. The
  14771.  function keys don't generate character codes. The F10 key invokes the menu
  14772.  in Presentation Manager programs, so VK_MENU is defined to be the same as
  14773.  VK_F10.
  14774.  
  14775.  The cursor movement keys generate the following virtual key codes:
  14776.  
  14777.     VK_LEFT       VK_UP         VK_PAGEUP     VK_HOME
  14778.     VK_RIGHT      VK_DOWN       VK_PAGEDOWN   VK_END
  14779.  
  14780.  The Insert and Delete keys generate the following virtual key codes:
  14781.  
  14782.     VK_DELETE
  14783.     VK_INSERT
  14784.  
  14785.  The IBM enhanced keyboard has a set of dedicated cursor movement keys and
  14786.  Insert and Delete. These keys always generate the virtual key codes just
  14787.  shown. The KC_CHAR flag is 0.
  14788.  
  14789.  The number pad on IBM keyboards can be used for either typing numbers or for
  14790.  cursor movement, or for Insert or Delete. If Num Lock is toggled off, the
  14791.  number pad generates virtual key codes and not character codes. If Num Lock
  14792.  is toggled on, the number pad generates the virtual key codes as well as
  14793.  character codes for numbers and the decimal point. For this reason, it's
  14794.  best to process character keys before virtual keys.
  14795.  
  14796.  The Shift key reverses the meaning of Num Lock for the number pad keys. The
  14797.  virtual key codes for the number pad are important only if your program
  14798.  needs to differentiate between characters from the number pad and the same
  14799.  characters generated otherwise.
  14800.  
  14801.  The Spacebar, Tab, Enter, and Backspace keys generate both virtual codes and
  14802.  character codes. Their virtual key codes are as follows:
  14803.  
  14804.     VK_SPACE      VK_NEWLINE
  14805.     VK_TAB        VK_ENTER
  14806.     VK_BACKSPACE
  14807.  
  14808.  The VK_NEWLINE code is generated from the Enter key on the main keyboard,
  14809.  and VK_ENTER is generated from the Enter key on the number pad of the IBM
  14810.  enhanced keyboard. You can process any of these five keys as virtual keys or
  14811.  character keys.
  14812.  
  14813.  One slightly problematic key combination is Shift-Tab. This combination
  14814.  generates a virtual key code of VK_BACKTAB. But the character code is the
  14815.  same as for an unshifted Tab key. If you differentiate between a Tab and a
  14816.  Shift-Tab, you'll want to process the VK_BACKTAB virtual key before
  14817.  processing character keys. Or you can check the state of the KC_SHIFT flag
  14818.  while processing Tab as a character key.
  14819.  
  14820.  Although an ASCII character code is defined for Escape, the Escape key
  14821.  generates only a virtual key code: VK_ESC.
  14822.  
  14823.  The following virtual key code identifiers are for the shift and toggle
  14824.  keys:
  14825.  
  14826.     VK_SHIFT      VK_NUMLOCK
  14827.     VK_CTRL       VK_SCRLLOCK
  14828.     VK_ALT        VK_ALTGRAF
  14829.     VK_CAPSLOCK
  14830.  
  14831.  The VK_ALTGRAF key is the right Alt key on some European versions of the IBM
  14832.  enhanced keyboard.
  14833.  
  14834.  Certain key combinations generate these virtual key codes:
  14835.  
  14836.     VK_BREAK      VK_PRINTSCRN
  14837.     VK_PAUSE      VK_SYSRQ
  14838.  
  14839.  Although the Presentation Manager does nothing with these key combinations,
  14840.  you may want to process them.
  14841.  
  14842.  Finally, there are three virtual key codes that you never receive with a
  14843.  WM_CHAR message:
  14844.  
  14845.     VK_BUTTON1    VK_BUTTON2    VK_BUTTON3
  14846.  
  14847.  These refer to mouse buttons. I discuss how to use these identifiers in the
  14848.  next chapter.
  14849.  
  14850.  Going Down, Going Up
  14851.  
  14852.  If the user simply presses and releases a key, the window procedure usually
  14853.  receives two WM_CHAR messages. The KC_KEYUP flag in the WM_CHAR mp1
  14854.  parameter indicates whether the message signals a key press or release.
  14855.  
  14856.                                KC_KEYUP
  14857.     Key is pressed             0
  14858.     Key is released            1
  14859.  
  14860.  For character keys, the KC_CHAR flag is set (and the character code is
  14861.  valid) for key presses. For the Alt key, the window procedure receives only
  14862.  one WM_CHAR message, for the key press. The frame window uses the release of
  14863.  the Alt key to activate the program's window. For the F1 and F10 keys, the
  14864.  window procedure receives a WM_CHAR message only for the release. The window
  14865.  procedure receives a WM_HELP message for the F1 key press.
  14866.  
  14867.  Often the key-down and key-up WM_CHAR messages come in pairs with nothing in
  14868.  between, but that's not always the case. For example, when the user presses
  14869.  the Shift key and a letter and then releases the letter and the Shift key,
  14870.  the program receives four WM_CHAR messages in this order:
  14871.  
  14872.                     KC_KEYUP    Virtual Key   Character
  14873.     Press Shift     0           VK_SHIFT      0
  14874.     Press A key     0           0             A
  14875.     Release A key   1           0             A
  14876.     Release Shift   1           VK_SHIFT      0
  14877.  
  14878.  For most purposes, you can ignore these WM_CHAR messages when the KC_KEYUP
  14879.  bit is set to 1. Thus the processing of the WM_CHAR message can include
  14880.  logic like this:
  14881.  
  14882.    case WM_CHAR:
  14883.         [other program lines]
  14884.         if (CHARMSG (&msg) -> fs &KC_KEYUP)
  14885.              return 0;
  14886.         [other program lines]
  14887.  
  14888.  
  14889.  If the user presses the key and holds it down, the program receives a series
  14890.  of WM_CHAR messages because of the typematic action of the key. This is
  14891.  indicated by the KC_PREVDOWN flag:
  14892.  
  14893.                           KC_KEYUP        KC_PREVDOWN
  14894.     Key is pressed        0               0
  14895.     Key is held down      0               1
  14896.     Key is released       1               0
  14897.  
  14898.  You receive one WM_CHAR message when the key is initially pressed, a series
  14899.  of messages as the key is held down, and a final WM_CHAR message when the
  14900.  key is released. A program can use the KC_PREVDOWN flag to distinguish
  14901.  between an initial key press and a typematic repeat of a key. Note that the
  14902.  KC_PREVDOWN flag is not set when the key is released, even though the key
  14903.  was previously down.
  14904.  
  14905.  The mp1 parameter also contains an 8-bit repeat count that you can extract
  14906.  with the expression
  14907.  
  14908.    CHAR3FROMMP (mp1)
  14909.  
  14910.  or
  14911.  
  14912.    CHARMSG (&msg) -> cRepeat
  14913.  
  14914.  Most often, this value is 1. It can be greater than 1 only for a typematic
  14915.  repeat, when the KC_KEYUP flag is 0 and KC_PREVDOWN is 1. A repeat count
  14916.  greater than 1 indicates that the keyboard hardware generated a typematic
  14917.  repeat of a keystroke while a WM_CHAR message for the same key was still in
  14918.  the message queue. What it really indicates is that your program can't keep
  14919.  up with the pace of typematic key repeats.
  14920.  
  14921.  How you handle the repeat count requires some thought. We've all experienced
  14922.  the nuisance of "overscrolling" a word-processing document or spreadsheet.
  14923.  By ignoring the repeat count, you avoid this problem. But you probably
  14924.  always want to use the repeat count when processing character input. This
  14925.  usually involves a simple for loop in the WM_CHAR processing:
  14926.  
  14927.    for (sRepeat = 0 ; sRepeat < CHARMSG (&msg) -> cRepeat; sRepeat++)
  14928.         {
  14929.              [process key]
  14930.         }
  14931.  
  14932.  The KC_LONEKEY flag is set only for a key release. It indicates that no
  14933.  other key was pressed between the time the key was pressed and released. You
  14934.  can ignore the KC_LONEKEY flag unless you think of a particular application
  14935.  for it.
  14936.  
  14937.  The Shift States
  14938.  
  14939.  The KC_SHIFT, KC_CTRL, and KC_ALT flags in the WM_CHAR mp1 parameter
  14940.  indicate the state of the Shift, Ctrl, and Alt keys at the time a key was
  14941.  pressed or released. When the flag is set to 1, it means that the shift key
  14942.  was pressed. You can also use the WinGetKeyState function (discussed later
  14943.  in this chapter) to obtain this information. When you process a character
  14944.  key, you don't have to look at the KC_SHIFT flag because the character code
  14945.  itself is based on the current state of the Shift key. The shift-state
  14946.  information is most useful during WM_CHAR messages for noncharacter keys,
  14947.  particularly the cursor movement keys.
  14948.  
  14949.  The KC_TOGGLE flag is most useful for the Caps Lock, Num Lock, and Scroll
  14950.  Lock keys if your program displays the current state of these keys. The
  14951.  KC_TOGGLE flag is set if the keystroke is turning on the lock state.
  14952.  However, you can treat any key as a toggle key by examining this flag.
  14953.  
  14954.  The KC_DEADKEY, KC_COMPOSITE, and KC_INVALIDCOMP flags are used with "dead
  14955.  keys" generated from some European keyboards. I'll discuss these flags in
  14956.  reference to the upcoming TYPEAWAY program.
  14957.  
  14958.  
  14959.  Other Keyboard Messages and Functions
  14960.  
  14961.  Although processing the WM_CHAR message is the most important part of
  14962.  keyboard handling, it's not the only part: Several other important concepts,
  14963.  messages, and functions relate to the keyboard. We'll look at these and then
  14964.  apply this information in a program that illustrates several aspects of
  14965.  keyboard handling.
  14966.  
  14967.  Active Windows and Focus Windows
  14968.  
  14969.  The keyboard must be shared among all applications running under the
  14970.  Presentation Manager. When a keyboard event occurs, the Presentation Manager
  14971.  stores the information about the event in its own system message queue. The
  14972.  Presentation Manager later converts this event to a WM_CHAR message posted
  14973.  to a particular program message queue for a particular window. The window
  14974.  that gets the WM_CHAR message is the window with the "input focus,"
  14975.  sometimes also called the "focus window."
  14976.  
  14977.  The concept of input focus is closely related to the concept of "active
  14978.  window." The active window is always a top-level window, that is, a child of
  14979.  the desktop window. The active window is positioned above all other
  14980.  top-level windows on the screen. A standard window frame indicates that it
  14981.  is active by highlighting its title bar. A dialog box indicates that it is
  14982.  active by highlighting its border. The user generally controls which window
  14983.  is active by using the Alt-Esc or Alt-Tab key combinations to switch from
  14984.  one window to another or by clicking on a particular window with the mouse.
  14985.  
  14986.  The focus window (if any) is always the active window itself or a descendant
  14987.  of the active window. The Presentation Manager posts WM_CHAR messages to the
  14988.  focus window. When a program first creates a standard window, the frame
  14989.  window is the active window, and the client window is the focus window.
  14990.  
  14991.  Thus in a standard window without any additional child windows, the client
  14992.  window procedure always receives WM_CHAR messages when the frame window is
  14993.  active. (If the program creates some children of the client window, these
  14994.  child windows can get the input focus. We'll examine this subject more in
  14995.  Chapter 11.) If a particular descendant of the active window has the input
  14996.  focus when the user changes the active window by pressing Alt-Esc or
  14997.  Alt-Tab, the same descendant regains the input focus when the frame window
  14998.  again becomes active.
  14999.  
  15000.  The Presentation Manager sends a WM_SETFOCUS message to a window procedure
  15001.  when the window is gaining the input focus or losing the input focus. A
  15002.  program can determine which window has the input focus by calling the
  15003.  WinQueryFocus function. We'll use this message and function in the TYPEAWAY
  15004.  program coming up soon.
  15005.  
  15006.  I mentioned at the beginning of this section that the Presentation Manager
  15007.  first stores keyboard messages in a system message queue. It does this
  15008.  because one of these messages (an Alt-Tab key combination, for instance)
  15009.  could change the active window and hence the window with the input focus.
  15010.  The messages for the keys that follow the Alt-Tab must go to a different
  15011.  program. This wouldn't work properly if the messages were posted in a
  15012.  program's message queue when the keystrokes occurred.
  15013.  
  15014.  Getting Keyboard States
  15015.  
  15016.  A program can obtain the state of a particular key at any time by calling
  15017.  
  15018.    sKeyState = WinGetKeyState (HWND_DESKTOP, sVirtKey) ;
  15019.  
  15020.  The sVirtKey parameter can be any of the virtual key identifiers beginning
  15021.  with VK. The sKeyState return value has the high bit set if the key is down.
  15022.  Because WinGetKeyState returns a signed short integer, you can determine if
  15023.  a key is down by simply testing if the return value is negative. The low bit
  15024.  is set if the key is toggled on. The low bit has little meaning for keys
  15025.  other than toggle keys, but it can allow you to treat any key as a toggle
  15026.  key.
  15027.  
  15028.  WinGetKeyState is synchronized with the WM_CHAR messages. It reports that a
  15029.  particular key is pressed or released only if the WM_CHAR message for the
  15030.  press or release has already been retrieved from the message queue. This
  15031.  synchronization is to your advantage: If you call WinGetKeyState during
  15032.  processing of a WM_CHAR message, it reports the state of keys at the time of
  15033.  the keyboard action that resulted in the message. If you need to know the
  15034.  state of a key "right now" rather than as of the most recent WM_CHAR
  15035.  message, you can call the WinGetPhysKeyState function instead.
  15036.  
  15037.  Using a Cursor
  15038.  
  15039.  When you process keystrokes, you often echo characters to the client window.
  15040.  To indicate where the next character will appear in the client window, a
  15041.  program can create a cursor.
  15042.  
  15043.  ───────────────────────────────────────────────────────────────────────────
  15044.  NOTE:
  15045.     The terminology used in Microsoft Windows is different from the
  15046.     Presentation Manager terminology. The small bitmap on the screen that you
  15047.     move with the mouse is called the "pointer." In Windows it is called the
  15048.     cursor. The Presentation Manager cursor is a small blinking box or line
  15049.     generally indicating an entry point for keyboard input. In Windows this
  15050.     is called the "caret." There are no carets in the Presentation Manager.
  15051.     You point with the mouse and curse with the keyboard.
  15052.  ───────────────────────────────────────────────────────────────────────────
  15053.  
  15054.  You create a cursor using the function
  15055.  
  15056.    WinCreateCursor (hwnd, xPos, yPos, cxWidth, cyHeight, fsFlags, &rclClip) ;
  15057.  
  15058.  The xPos and yPos parameters indicate where the lower-left corner of the
  15059.  cursor is to appear relative to the lower-left corner of hwnd. Generally,
  15060.  the lower-left corner of the cursor corresponds to the lower-left corner of
  15061.  a character cell.
  15062.  
  15063.  The cxWidth and cyHeight parameters are the size of the cursor. You can use
  15064.  0 for either of the two parameters to set the size equal to the width of a
  15065.  thin border. These are the most common combinations of cxWidth and cyHeight
  15066.  (based on cxChar and cyChar character dimensions):
  15067.  
  15068.     cxWidth           cyHeight          Cursor Form
  15069.     cxChar            cyChar            Box
  15070.     cxChar            0                 Underline
  15071.     0                 cyChar            Vertical line
  15072.  
  15073.  The box and underline cursors most closely mimic cursors in nongraphics
  15074.  programs. The vertical line cursor is the best suited for use with a font
  15075.  with variable character widths because you can position the vertical line
  15076.  between two adjacent characters. If you use a box or underline cursor with a
  15077.  variable-pitch font, you have to change the width of the cursor as it's
  15078.  moved over the characters. This requires that you destroy and recreate the
  15079.  cursor──a nuisance for you, and an annoyance to the user, who would be
  15080.  faced with a pulsating cursor.
  15081.  
  15082.  The fsFlags parameter can be CURSOR_SOLID (which equals 0, so it's the
  15083.  default) for a solid cursor or CURSOR_HALFTONE for a cursor with only half
  15084.  the bits present. You can use the C bitwise OR operator to include the
  15085.  CURSOR_FLASH flag and make a blinking cursor. If you create a box cursor,
  15086.  you can include the CURSOR_FRAME flag to draw only the frame of the cursor
  15087.  and not the interior.
  15088.  
  15089.  The last parameter to WinCreateCursor is a pointer to a RECTL structure,
  15090.  which defines a clipping region relative to hwnd. The cursor won't be
  15091.  visible outside this rectangle. Specifying NULL for this parameter sets the
  15092.  clipping region equal to the entire area of the window at the time of the
  15093.  WinCreateCursor call.
  15094.  
  15095.  When the cursor is first created, it is invisible. You can show it by
  15096.  calling
  15097.  
  15098.    WinShowCursor (hwnd, TRUE) ;
  15099.  
  15100.  You can hide the cursor by calling
  15101.  
  15102.    WinShowCursor (hwnd, FALSE) ;
  15103.  
  15104.  You need to hide the cursor when you write to the screen during a message
  15105.  other than WM_PAINT.
  15106.  
  15107.  After the cursor is created, you can change the position with another call
  15108.  to WinCreateCursor:
  15109.  
  15110.    WinCreateCursor (hwnd, xPos, yPos, 0, 0, CURSOR_SETPOS, NULL) ;
  15111.  
  15112.  This is a special version of the WinCreateCursor call. The size and clipping
  15113.  region parameters are ignored. The only flag you can use is CURSOR_SETPOS.
  15114.  (Do not, however, use CURSOR_SETPOS when you are creating the cursor.)
  15115.  
  15116.  Finally, to destroy the cursor, you call
  15117.  
  15118.    WinDestroyCursor (hwnd) ;
  15119.  
  15120.  When using a cursor, you must remember this very important rule: Only one
  15121.  cursor can be present in the Presentation Manager at any time. Do not create
  15122.  a cursor during the WM_CREATE message and destroy it during WM_DESTROY.
  15123.  Instead, you create the cursor when the window gets the input focus and
  15124.  destroy the cursor when the window loses the input focus. And take note of
  15125.  this: If the clipping region of the cursor depends on the size of the window
  15126.  (as it does if you specify NULL as the last parameter to WinCreateCursor
  15127.  when you create the cursor), you should destroy and recreate the cursor when
  15128.  you receive a WM_SIZE message. This is the only way to change the clipping
  15129.  region of the cursor.
  15130.  
  15131.  The cursor logic can be tricky, so let's look at the code involved with
  15132.  maintaining a cursor in the context of a program that also does other
  15133.  keyboard handling.
  15134.  
  15135.  Sample Keyboard Processing
  15136.  
  15137.  The TYPEAWAY program, shown in Figure 8-5, demonstrates several of the
  15138.  concepts covered in this chapter. When TYPEAWAY's window first appears, the
  15139.  cursor is positioned in the upper-left corner of the client window. To use
  15140.  the program, simply type away. What you type is what you see.
  15141.  
  15142.  Figure 8-5.  The TYPEAWAY program.
  15143.  
  15144.    The TYPEAWAY File
  15145.  
  15146.    #--------------------
  15147.    # TYPEAWAY make file
  15148.    #--------------------
  15149.  
  15150.    typeaway.obj : typeaway.c
  15151.         cl -c -G2sw -W3 typeaway.c
  15152.  
  15153.    easyfont.obj : easyfont.c
  15154.         cl -c -G2sw -W3 easyfont.c
  15155.  
  15156.    typeaway.exe : typeaway.obj easyfont.obj typeaway.def
  15157.         link typeaway easyfont, /align:16, NUL, os2, typeaway
  15158.  
  15159.    The TYPEAWAY.C File
  15160.  
  15161.    /*------------------------------
  15162.       TYPEAWAY.C -- Typing Program
  15163.      ------------------------------*/
  15164.  
  15165.    #define INCL_WIN
  15166.    #define INCL_GPI
  15167.    #include <os2.h>
  15168.    #include <stdio.h>
  15169.    #include <stdlib.h>
  15170.    #include "easyfont.h"
  15171.  
  15172.    #define LCID_FIXEDFONT 1L
  15173.    #define BUFFER(x,y) (*(pBuffer + y * xMax + x))
  15174.  
  15175.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  15176.  
  15177.    CHAR szClientClass [] = "TypeAway" ;
  15178.    HAB  hab ;
  15179.  
  15180.    int main (void)
  15181.         {
  15182.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  15183.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  15184.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  15185.         HMQ          hmq ;
  15186.         HWND         hwndFrame, hwndClient ;
  15187.         QMSG         qmsg ;
  15188.  
  15189.         hab = WinInitialize (0) ;
  15190.         hmq = WinCreateMsgQueue (hab, 0) ;
  15191.  
  15192.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  15193.  
  15194.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  15195.                                         &flFrameFlags, szClientClass, NULL,
  15196.                             0L, NULL, 0, &hwndClient) ;
  15197.         if (hwndFrame != NULL)
  15198.              {
  15199.              WinSendMsg (hwndFrame, WM_SETICON,
  15200.                          WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE
  15201.                          NULL) ;
  15202.  
  15203.              while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  15204.                   WinDispatchMsg (hab, &qmsg) ;
  15205.  
  15206.              WinDestroyWindow (hwndFrame) ;
  15207.              }
  15208.         WinDestroyMsgQueue (hmq) ;
  15209.         WinTerminate (hab) ;
  15210.         return 0 ;
  15211.         }
  15212.  
  15213.    VOID GetCharXY (HPS hps, SHORT *pcxChar, SHORT *pcyChar, SHORT *pcyDesc)
  15214.         {
  15215.         FONTMETRICS fm ;
  15216.  
  15217.         GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  15218.         *pcxChar = (SHORT) fm.lAveCharWidth ;
  15219.         *pcyChar = (SHORT) fm.lMaxBaselineExt ;
  15220.         *pcyDesc = (SHORT) fm.lMaxDescender ;
  15221.         }
  15222.  
  15223.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  15224.         {
  15225.         static BOOL  fInsertMode = FALSE ;
  15226.         static CHAR  *pBuffer ;
  15227.         static SHORT cxClient, cyClient, cxChar, cyChar, cyDesc,
  15228.                      xCursor, yCursor, xMax,  yMax ;
  15229.         BOOL         fProcessed ;
  15230.         CHAR         szBuffer [20] ;
  15231.         HPS          hps ;
  15232.         POINTL       ptl ;
  15233.         RECTL        rcl ;
  15234.         SHORT        sRep, s ;
  15235.  
  15236.         switch (msg)
  15237.              {
  15238.              case WM_CREATE:
  15239.                   hps = WinGetPS (hwnd) ;
  15240.                   EzfQueryFonts (hps) ;
  15241.  
  15242.                   if (!EzfCreateLogFont (hps, LCID_FIXEDFONT, FONTFACE_COUR,
  15243.                                                               FONTSIZE_10, 0))
  15244.                        {
  15245.                        WinReleasePS (hps) ;
  15246.  
  15247.                        WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
  15248.                             "Cannot find a fixed-pitch font.  Load the Courier
  15249.                             "fonts from the Control Panel and try again.",
  15250.                             szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;
  15251.  
  15252.                        return 1 ;
  15253.                        }
  15254.  
  15255.                   GpiSetCharSet (hps, LCID_FIXEDFONT) ;
  15256.  
  15257.                   GetCharXY (hps, &cxChar, &cyChar, &cyDesc) ;
  15258.  
  15259.                   GpiSetCharSet (hps, LCID_DEFAULT) ;
  15260.                   GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
  15261.                   WinReleasePS (hps) ;
  15262.                   return 0 ;
  15263.  
  15264.              case WM_SIZE:
  15265.                   cxClient = SHORT1FROMMP (mp2) ;
  15266.                   cyClient = SHORT2FROMMP (mp2) ;
  15267.  
  15268.                   xMax = min (255, cxClient / cxChar) ;
  15269.                   yMax = min (255, cyClient / cyChar - 2) ;
  15270.  
  15271.                   if (pBuffer != NULL)
  15272.                        free (pBuffer) ;
  15273.  
  15274.                   if (NULL == (pBuffer = malloc (xMax * yMax + 1)))
  15275.                        {
  15276.                        WinMessageBox (HWND_DESKTOP, hwnd,
  15277.                             "Cannot allocate memory for text buffer.\n"
  15278.                             "Try a smaller window.", szClientClass, 0,
  15279.                             MB_OK | MB_ICONEXCLAMATION) ;
  15280.  
  15281.                        xMax = yMax = 0 ;
  15282.                        }
  15283.                   else
  15284.                        {
  15285.                        for (s = 0 ; s < xMax * yMax ; BUFFER (s++, 0) = ' ') ;
  15286.  
  15287.                        xCursor = 0 ;
  15288.                        yCursor = 0 ;
  15289.                        }
  15290.  
  15291.                   if (hwnd == WinQueryFocus (HWND_DESKTOP, FALSE))
  15292.                        {
  15293.                        WinDestroyCursor (hwnd) ;
  15294.  
  15295.                        WinCreateCursor (hwnd, 0, cyClient - cyChar,
  15296.                                         cxChar, cyChar,
  15297.                                         CURSOR_SOLID | CURSOR_FLASH, NULL) ;
  15298.  
  15299.                        WinShowCursor (hwnd, xMax > 0 && yMax > 0) ;
  15300.                        }
  15301.                   return 0 ;
  15302.  
  15303.              case WM_SETFOCUS:
  15304.                   if (SHORT1FROMMP (mp2))
  15305.                        {
  15306.                        WinCreateCursor (hwnd, cxChar * xCursor,
  15307.                                         cyClient - cyChar * (1 + yCursor),
  15308.                                         cxChar, cyChar,
  15309.                                         CURSOR_SOLID | CURSOR_FLASH, NULL) ;
  15310.  
  15311.                        WinShowCursor (hwnd, xMax > 0 && yMax > 0) ;
  15312.                        }
  15313.                   else
  15314.                        WinDestroyCursor (hwnd) ;
  15315.                   return 0 ;
  15316.  
  15317.              case WM_CHAR:
  15318.                   if (xMax == 0 || yMax == 0)
  15319.                        return 0 ;
  15320.  
  15321.                   if (CHARMSG(&msg)->fs & KC_KEYUP)
  15322.                        return 0 ;
  15323.  
  15324.                   if (CHARMSG(&msg)->fs & KC_INVALIDCHAR)
  15325.                        return 0 ;
  15326.  
  15327.                   if (CHARMSG(&msg)->fs & KC_INVALIDCOMP)
  15328.                        {
  15329.                        xCursor = (xCursor + 1) % xMax ;        // Advance curs
  15330.                        if (xCursor == 0)
  15331.                             yCursor = (yCursor + 1) % yMax ;
  15332.  
  15333.                        WinAlarm (HWND_DESKTOP, WA_ERROR) ;     // And beep
  15334.                        }
  15335.  
  15336.                   for (sRep = 0 ; sRep < CHARMSG(&msg)->cRepeat ; sRep++)
  15337.                        {
  15338.                        fProcessed = FALSE ;
  15339.  
  15340.                        ptl.x = xCursor * cxChar ;
  15341.                        ptl.y = cyClient - cyChar * (yCursor + 1) + cyDesc ;
  15342.  
  15343.                                  /*---------------------------
  15344.                                     Process some virtual keys
  15345.                                    ---------------------------*/
  15346.  
  15347.                        if (CHARMSG(&msg)->fs & KC_VIRTUALKEY)
  15348.                             {
  15349.                             fProcessed = TRUE ;
  15350.  
  15351.                             switch (CHARMSG(&msg)->vkey)
  15352.                                  {
  15353.                                            /*---------------
  15354.                                               Backspace key
  15355.                                              ---------------*/
  15356.  
  15357.                                  case VK_BACKSPACE:
  15358.                                       if (xCursor > 0)
  15359.                                            {
  15360.                                            WinSendMsg (hwnd, WM_CHAR,
  15361.                                                 MPFROM2SHORT (KC_VIRTUALKEY, 1
  15362.                                                 MPFROM2SHORT (0, VK_LEFT)) ;
  15363.  
  15364.                                            WinSendMsg (hwnd, WM_CHAR,
  15365.                                                 MPFROM2SHORT (KC_VIRTUALKEY, 1
  15366.                                                 MPFROM2SHORT (0, VK_DELETE)) ;
  15367.                                            }
  15368.                                       break ;
  15369.  
  15370.                                            /*---------
  15371.                                               Tab key
  15372.                                              ---------*/
  15373.  
  15374.                                  case VK_TAB:
  15375.                                       s = min (8 - xCursor % 8, xMax - xCursor
  15376.  
  15377.                                       WinSendMsg (hwnd, WM_CHAR,
  15378.                                            MPFROM2SHORT (KC_CHAR, s),
  15379.                                            MPFROM2SHORT ((USHORT) ' ', 0)) ;
  15380.  
  15381.                                       break ;
  15382.  
  15383.                                            /*-------------------------
  15384.                                               Backtab (Shift-Tab) key
  15385.                                              -------------------------*/
  15386.  
  15387.                                  case VK_BACKTAB:
  15388.                                       if (xCursor > 0)
  15389.                                            {
  15390.                                            s = (xCursor - 1) % 8 + 1 ;
  15391.  
  15392.                                            WinSendMsg (hwnd, WM_CHAR,
  15393.                                                 MPFROM2SHORT (KC_VIRTUALKEY, s
  15394.                                                 MPFROM2SHORT (0, VK_LEFT)) ;
  15395.                                            }
  15396.                                       break ;
  15397.  
  15398.                                            /*------------------------
  15399.                                               Newline and Enter keys
  15400.                                              ------------------------*/
  15401.  
  15402.                                  case VK_NEWLINE:
  15403.                                  case VK_ENTER:
  15404.                                       xCursor = 0 ;
  15405.                                       yCursor = (yCursor + 1) % yMax ;
  15406.                                       break ;
  15407.  
  15408.                                  default:
  15409.                                       fProcessed = FALSE ;
  15410.                                       break ;
  15411.                                  }
  15412.                             }
  15413.  
  15414.                                  /*------------------------
  15415.                                     Process character keys
  15416.                                    ------------------------*/
  15417.  
  15418.                        if (!fProcessed && CHARMSG(&msg)->fs & KC_CHAR)
  15419.                             {
  15420.                                                      // Shift line if fInsertM
  15421.                             if (fInsertMode)
  15422.                                  for (s = xMax - 1 ; s > xCursor ; s--)
  15423.                                       BUFFER (s, yCursor) =
  15424.                                            BUFFER (s - 1, yCursor) ;
  15425.  
  15426.                                                      // Store character in buf
  15427.  
  15428.                             BUFFER (xCursor, yCursor) =
  15429.                                                 (CHAR) CHARMSG(&msg)->chr ;
  15430.  
  15431.                                                      // Display char or new li
  15432.  
  15433.                             WinShowCursor (hwnd, FALSE) ;
  15434.                             hps = WinGetPS (hwnd) ;
  15435.  
  15436.                             EzfCreateLogFont (hps, LCID_FIXEDFONT,
  15437.                                               FONTFACE_COUR, FONTSIZE_10, 0) ;
  15438.                             GpiSetCharSet (hps, LCID_FIXEDFONT) ;
  15439.                             GpiSetBackMix (hps, BM_OVERPAINT) ;
  15440.  
  15441.                             if (fInsertMode)
  15442.                                  GpiCharStringAt (hps, &ptl,
  15443.                                                   (LONG) (xMax - xCursor),
  15444.                                                   & BUFFER (xCursor, yCursor))
  15445.                             else
  15446.                                  GpiCharStringAt (hps, &ptl, 1L,
  15447.                                                   (CHAR *) & CHARMSG(&msg)->ch
  15448.  
  15449.                             GpiSetCharSet (hps, LCID_DEFAULT) ;
  15450.                             GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
  15451.                             WinReleasePS (hps) ;
  15452.                             WinShowCursor (hwnd, TRUE) ;
  15453.  
  15454.                                                      // Increment cursor
  15455.  
  15456.                             if (!(CHARMSG(&msg)->fs & KC_DEADKEY))
  15457.                                  if (0 == (xCursor = (xCursor + 1) % xMax))
  15458.                                       yCursor = (yCursor + 1) % yMax ;
  15459.  
  15460.                             fProcessed = TRUE ;
  15461.                             }
  15462.  
  15463.                                  /*--------------------------------
  15464.                                     Process remaining virtual keys
  15465.                                    --------------------------------*/
  15466.  
  15467.                        if (!fProcessed && CHARMSG(&msg)->fs & KC_VIRTUALKEY)
  15468.                             {
  15469.                             fProcessed = TRUE ;
  15470.  
  15471.                             switch (CHARMSG(&msg)->vkey)
  15472.                                  {
  15473.                                            /*----------------------
  15474.                                               Cursor movement keys
  15475.                                              ----------------------*/
  15476.  
  15477.                                  case VK_LEFT:
  15478.                                       xCursor = (xCursor - 1 + xMax) % xMax ;
  15479.  
  15480.                                       if (xCursor == xMax - 1)
  15481.                                            yCursor = (yCursor - 1 + yMax) % yM
  15482.                                       break ;
  15483.  
  15484.                                  case VK_RIGHT:
  15485.                                       xCursor = (xCursor + 1) % xMax ;
  15486.  
  15487.                                       if (xCursor == 0)
  15488.                                            yCursor = (yCursor + 1) % yMax ;
  15489.                                       break ;
  15490.  
  15491.                                  case VK_UP:
  15492.                                       yCursor = max (yCursor - 1, 0) ;
  15493.                                       break ;
  15494.  
  15495.                                  case VK_DOWN:
  15496.                                       yCursor = min (yCursor + 1, yMax - 1) ;
  15497.                                       break ;
  15498.  
  15499.                         case VK_PAGEUP:
  15500.                                       yCursor = 0 ;
  15501.                                       break ;
  15502.  
  15503.                         case VK_PAGEDOWN:
  15504.                                       yCursor = yMax - 1 ;
  15505.                                       break ;
  15506.  
  15507.                                  case VK_HOME:
  15508.                                       xCursor = 0 ;
  15509.                                       break ;
  15510.  
  15511.                                  case VK_END:
  15512.                                       xCursor = xMax - 1 ;
  15513.                                       break ;
  15514.  
  15515.                                            /*------------
  15516.                                               Insert key
  15517.                                              ------------*/
  15518.  
  15519.                                  case VK_INSERT:
  15520.                                       fInsertMode = fInsertMode ? FALSE : TRUE
  15521.                                       WinSetRect (hab, &rcl, 0, 0,
  15522.                                                   cxClient, cyChar) ;
  15523.                                       WinInvalidateRect (hwnd, &rcl, FALSE) ;
  15524.                                       break ;
  15525.  
  15526.                                            /*------------
  15527.                                               Delete key
  15528.                                              ------------*/
  15529.  
  15530.                         case VK_DELETE:
  15531.                              for (s = xCursor ; s < xMax - 1 ; s++)
  15532.                                   BUFFER (s, yCursor) =
  15533.                                        BUFFER (s + 1, yCursor) ;
  15534.  
  15535.                              BUFFER (xMax, yCursor) = ' ' ;
  15536.  
  15537.                              WinShowCursor (hwnd, FALSE) ;
  15538.                              hps = WinGetPS (hwnd) ;
  15539.                              EzfCreateLogFont (hps, LCID_FIXEDFONT,
  15540.                                        FONTFACE_COUR, FONTSIZE_10, 0) ;
  15541.                              GpiSetCharSet (hps, LCID_FIXEDFONT) ;
  15542.                              GpiSetBackMix (hps, BM_OVERPAINT) ;
  15543.  
  15544.                              GpiCharStringAt (hps, &ptl,
  15545.                                        (LONG) (xMax - xCursor),
  15546.                                        & BUFFER (xCursor, yCursor)) ;
  15547.  
  15548.                              GpiSetCharSet (hps, LCID_DEFAULT) ;
  15549.                              GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
  15550.                              WinReleasePS (hps) ;
  15551.                              WinShowCursor (hwnd, TRUE) ;
  15552.                              break ;
  15553.  
  15554.                                  default:
  15555.                                       fProcessed = FALSE ;
  15556.                                       break ;
  15557.                                  }
  15558.                             }
  15559.                        }
  15560.                   WinCreateCursor (hwnd, cxChar * xCursor,
  15561.                                          cyClient - cyChar * (1 + yCursor),
  15562.                                          0, 0, CURSOR_SETPOS, NULL) ;
  15563.                   return 0 ;
  15564.  
  15565.              case WM_PAINT:
  15566.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  15567.                   GpiErase (hps) ;
  15568.                   EzfCreateLogFont (hps, LCID_FIXEDFONT, FONTFACE_COUR,
  15569.                                                          FONTSIZE_10, 0) ;
  15570.                   GpiSetCharSet (hps, LCID_FIXEDFONT) ;
  15571.  
  15572.                   ptl.x = cxChar ;
  15573.                   ptl.y = cyDesc ;
  15574.                   GpiCharStringAt (hps, &ptl,
  15575.                                    (LONG) sprintf (szBuffer, "Insert Mode: %s"
  15576.                                                    fInsertMode ? "ON" : "OFF")
  15577.                                    szBuffer) ;
  15578.  
  15579.                   ptl.x = 0 ;
  15580.                   ptl.y = 3 * cyChar / 2 ;
  15581.                   GpiMove (hps, &ptl) ;
  15582.  
  15583.                   ptl.x = cxClient ;
  15584.                   GpiLine (hps, &ptl) ;
  15585.  
  15586.                   if (xMax > 0 && yMax > 0)
  15587.                        {
  15588.                        for (s = 0 ; s < yMax ; s++)
  15589.                             {
  15590.                             ptl.x = 0 ;
  15591.                             ptl.y = cyClient - cyChar * (s + 1) + cyDesc ;
  15592.  
  15593.                             GpiCharStringAt (hps, &ptl, (LONG) xMax,
  15594.                                                         & BUFFER (0, s)) ;
  15595.  
  15596.                             }
  15597.                        }
  15598.                   GpiSetCharSet (hps, LCID_DEFAULT) ;
  15599.                   GpiDeleteSetId (hps, LCID_FIXEDFONT) ;
  15600.                   WinEndPaint (hps) ;
  15601.                   return 0 ;
  15602.  
  15603.              case WM_DESTROY:
  15604.                   if (pBuffer != NULL)
  15605.                        free (pBuffer) ;
  15606.                   break ;
  15607.              }
  15608.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  15609.         }
  15610.  
  15611.    The TYPEAWAY.DEF File
  15612.  
  15613.    ;-------------------------------------
  15614.    ; TYPEAWAY.DEF module definition file
  15615.    ;-------------------------------------
  15616.  
  15617.    NAME           TYPEAWAY  WINDOWAPI
  15618.  
  15619.    DESCRIPTION    'Typing Program (C) Charles Petzold, 1988'
  15620.    PROTMODE
  15621.    HEAPSIZE       1024
  15622.    STACKSIZE      8192
  15623.    EXPORTS        ClientWndProc
  15624.  
  15625.  
  15626.  TYPEAWAY uses a fixed-pitch Courier font and requires the EASYFONT.C
  15627.  EASYFONT.H files shown in Chapter 5.
  15628.  
  15629.  You can move the cursor to any position within the client window using the
  15630.  cursor movement keys. They work as follows:
  15631.  
  15632.     Key             Cursor Movement
  15633.     Right Arrow     One character right (wraps to next line)
  15634.     Left Arrow      One character left (wraps to previous line)
  15635.     Down Arrow      One line down
  15636.     Up Arrow        One line up
  15637.  
  15638.     Key           Cursor Movement
  15639.     Home          Beginning of line
  15640.     End           End of line
  15641.     Page Up       Top line (same column position)
  15642.     Page Down     Bottom line (same column position)
  15643.  
  15644.  TYPEAWAY also processes the following keys:
  15645.  
  15646.     Key           Action
  15647.     Insert        Turn Insert mode on and off
  15648.     Delete        Delete character at cursor position, move rest of line to
  15649.                   left
  15650.     Return        Move cursor to beginning of next line
  15651.     Tab           Move cursor to next tab position based on 8-column
  15652.                   increments
  15653.     Shift-Tab     Move cursor to previous tab position
  15654.     Backspace     Delete character to left of cursor, move rest of line to
  15655.                   left
  15656.  
  15657.  The characters you type are stored in a block of memory allocated using
  15658.  malloc. This allows TYPEAWAY to re-create the client window when the window
  15659.  procedure receives a WM_PAINT message. TYPEAWAY frees this memory block and
  15660.  allocates a new one whenever the size of the window changes. This means that
  15661.  the contents of the buffer aren't preserved following a WM_SIZE message.
  15662.  
  15663.  Let's first isolate the cursor logic, because that is perhaps the trickiest
  15664.  to deal with. Cursor creation and destruction occur during processing of the
  15665.  WM_SETFOCUS message:
  15666.  
  15667.    case WM_SETFOCUS:
  15668.         if (SHORT1FROMMP  (mp2))
  15669.              {
  15670.              WinCreateCursor (hwnd, cxChar * xCursor,
  15671.                               cyClient - cyChar * (1 + yCursor,
  15672.                               cxChar, cyChar,
  15673.                               CURSOR_SOLID | CURSOR_FLASH, NULL) ;
  15674.  
  15675.              WinShowCursor (hwnd, xMax > 0 && yMax > 0) ;
  15676.              }
  15677.         else
  15678.              WinDestroyCursor (hwnd) ;
  15679.         return 0 ;
  15680.  
  15681.  The mp2 parameter is nonzero if the window is getting the input focus and 0
  15682.  if it's losing the input focus. These two types of WM_SETFOCUS messages are
  15683.  equally balanced during the lifetime of a window. This ensures that the
  15684.  program doesn't attempt to create a second cursor or destroy a nonexistent
  15685.  cursor. The window loses the input focus before it's destroyed, at which
  15686.  time the cursor will also be destroyed.
  15687.  
  15688.  When the TYPEAWAY client window receives the input focus, it creates a solid
  15689.  blinking cursor and positions it based on the size of the client area, the
  15690.  size of a character, and the cursor position (in terms of a row and column)
  15691.  stored in the variables xCursor and yCursor. The WinShowCursor function
  15692.  normally requires TRUE as the second parameter to display the cursor. The
  15693.  xMax and yMax variables are the number of character columns and rows in the
  15694.  client window, so this code displays the cursor only if the client window
  15695.  can fit at least one character.
  15696.  
  15697.  The cursor is also destroyed and re-created during processing of the WM_SIZE
  15698.  message. This is necessary to change the clipping region of the cursor when
  15699.  the window size changes. But note that TYPEAWAY does this only if the client
  15700.  window has the input focus. Otherwise, the cursor doesn't exist and will be
  15701.  re-created during the next WM_SETFOCUS message.
  15702.  
  15703.    if (hwnd == WinQueryFocus (HWND_DESKTOP, FALSE))
  15704.         {
  15705.         WinDestroyCursor (hwnd) ;
  15706.  
  15707.         WinCreateCursor (hwnd, 0, cyClient - cyChar,
  15708.                          cxChar, cyChar,
  15709.                          CURSOR_SOLID | CURSOR_FLASH, NULL) ;
  15710.  
  15711.         WinShowCursor (hwnd, xMax > 0 && yMax > 0) ;
  15712.         }
  15713.  
  15714.  The cursor is automatically hidden during a WM_PAINT message. This prevents
  15715.  a program from writing over the cursor. However, if you write on the window
  15716.  during messages other than WM_PAINT (as TYPEAWAY does), you must hide and
  15717.  show the cursor. TYPEAWAY writes on the window during WM_CHAR. Before
  15718.  calling WinGetPS, the cursor is hidden:
  15719.  
  15720.    WinShowCursor (hwnd, FALSE) ;
  15721.  
  15722.  After a call to WinReleasePS, the cursor is shown again:
  15723.  
  15724.    WinShowCursor (hwnd, TRUE) ;
  15725.  
  15726.  After the key has been processed and the character (if any) written to the
  15727.  client window, the cursor is repositioned:
  15728.  
  15729.    WinCreateCursor (hwnd, cxChar * xCursor,
  15730.                     cyClient - cyChar * (1 + yCursor),
  15731.                     0, 0, CURSOR_SETPOS, NULL) ;
  15732.  
  15733.  The processing of the WM_CHAR message is fairly straightfoward and easy to
  15734.  follow because of the switch and case structure. The program first checks to
  15735.  see that at least one character can fit in the client window:
  15736.  
  15737.    if (xMax == 0 || yMax == 0)
  15738.         return 0 ;
  15739.  
  15740.  It then checks to see that the message is for a key press:
  15741.  
  15742.    if (CHARMSG (&msg) -> fs & KC_KEYUP)
  15743.         return 0 ;
  15744.  
  15745.  Most of the key processing logic is repeated based on the repeat count:
  15746.  
  15747.    for (sRep = 0 ; sRep < CHARMSG (&msg) -> cRepeat ; sRep++)
  15748.         {
  15749.  
  15750.  TYPEAWAY also throws away WM_CHAR messages whenever the KC_INVALIDCHAR flag
  15751.  is set. (This is rarely the case.)
  15752.  
  15753.  I've chosen to process some virtual keys first. These are the Backspace,
  15754.  Tab, Shift-Tab, and Enter keys, which also generate character codes.
  15755.  
  15756.  The Backspace, Tab, and Shift-Tab keys are processed by sending the window
  15757.  function other WM_CHAR messages. This simplifies the logic for these keys.
  15758.  The character keys are processed next by displaying the character at the
  15759.  current cursor position. Then, the remaining virtual keys (cursor movement
  15760.  keys, Insert, and Delete) are processed.
  15761.  
  15762.  Dead Keys and Foreign Language Keyboards
  15763.  
  15764.  TYPEAWAY shows the correct processing of "dead keys" and "composite keys."
  15765.  These keys are generated on some foreign language keyboards to create
  15766.  characters containing diacritics (sometimes called accent marks). These
  15767.  characters require two keystrokes. The first keystroke is the diacritic
  15768.  itself and is called a "dead key." The second keystroke is a letter and is
  15769.  called a "composite key." The letter is combined with the diacritic mark to
  15770.  form a composite character.
  15771.  
  15772.  You can process dead keys and composite keys using the KC_DEADKEY,
  15773.  KC_COMPOSITE, and KC_INVALIDCOMP flags that accompany the WM_CHAR message.
  15774.  This will allow your program to be converted more easily to a foreign
  15775.  language. If foreign language conversion is not of concern to you, you can
  15776.  ignore these flags. A compromise approach is to throw away WM_CHAR messages
  15777.  when the KC_DEADKEY flag is set. Near the beginning of your WM_CHAR
  15778.  processing, you'd have
  15779.  
  15780.    if (CHARMSG(&msg)->fs & KC_DEADKEY)
  15781.         return 0 ;
  15782.  
  15783.  But this doesn't give good feedback to the user or provide error processing
  15784.  of incorrect combinations of dead keys and letters.
  15785.  
  15786.  If you add dead-key logic to your program, you'll need to test the logic.
  15787.  You must make the Presentation Manager believe that it is running on a
  15788.  foreign language keyboard that uses dead keys (for example, the German
  15789.  keyboard). You can do this by adding (or changing) the following statements
  15790.  in your CONFIG.SYS file:
  15791.  
  15792.    COUNTRY  = 049
  15793.    CODEPAGE = 850, 437
  15794.    DEVINFO  = KBD,  GR, [path] KEYBOARD.DCP
  15795.    DEVINFO  = SCR, EGA, [path] VIOTBL.DCP
  15796.  
  15797.  [path] is the path where the KEYBOARD.DCP and VIOTBL.DCP files are located.
  15798.  If you have a VGA rather than an EGA, use VGA in the second DEVINFO
  15799.  statement.
  15800.  
  15801.  After you reboot your system, you'll probably find that using this German
  15802.  keyboard is not easy. The Y and Z keys are reversed, and all the symbols are
  15803.  in different places. You can switch to the U.S. keyboard and codepage using
  15804.  the following OS/2 commands:
  15805.  
  15806.    KEYB US
  15807.    CHCP 437
  15808.  
  15809.  When you want to switch to German for running KEYLOOK or TYPEAWAY or to test
  15810.  one of your own programs, run
  15811.  
  15812.    KEYB GR
  15813.    CHCP 850
  15814.  
  15815.  Here's how dead keys work: A German user who wishes to type a letter with a
  15816.  diacritic first presses the dead key. The key corresponding to the + and =
  15817.  key on the U.S. keyboard generates dead keys on the German keyboard. When
  15818.  unshifted, the dead key is an acute diacritic (\ae). When shifted, the dead
  15819.  key is a grave diacritic (\ge). A Presentation Manager program should
  15820.  display this diacritic but not advance the cursor.
  15821.  
  15822.  The user then follows this dead key with an uppercase or lowercase A, E, I,
  15823.  O, or U. The resultant character is the letter with the diacritic. The
  15824.  program displays this character and advances the cursor. If the user wants
  15825.  to type the acute or grave mark by itself, he or she follows the dead key by
  15826.  pressing the Spacebar. A dead key followed by any other key is considered an
  15827.  error, and the program should indicate this by beeping. In this case, your
  15828.  program should advance the cursor past the diacritic and display the new key
  15829.  anyway, just as if the dead key were followed by the Spacebar and then the
  15830.  new key.
  15831.  
  15832.  The code in TYPEAWAY that is necessary to correctly handle dead-key
  15833.  combinations is not very large. You can consider three cases:
  15834.  
  15835.  Case 1: If the KC_CHAR and KC_DEADKEY flags are set, the character code is
  15836.  the code for the diacritic. You display this character, but do not advance
  15837.  the cursor. In TYPEAWAY, this is handled at the end of the section that
  15838.  processes character keys. The character is stored in the buffer and
  15839.  displayed, but the cursor is advanced only if the KC_DEADKEY flag is not
  15840.  set.
  15841.  
  15842.  Case 2: If the KC_CHAR and KC_COMPOSITE flags are set, the character
  15843.  accompanying the message will be the composite character. (If the dead key
  15844.  is followed by a Spacebar, the character code accompanying the WM_CHAR
  15845.  message for the Spacebar is the previous dead-key character.) You display
  15846.  the character and advance the cursor. This is exactly how you process a
  15847.  normal character key, so you do not need to check the KC_COMPOSITE flag.
  15848.  TYPEAWAY ignores it.
  15849.  
  15850.  Case 3: If the KC_INVALIDCOMP flag is set, the dead key was followed by a
  15851.  character or virtual key that cannot be combined with the dead key. You
  15852.  advance the cursor past the dead key and beep the speaker to indicate an
  15853.  error. Then you process the WM_CHAR message as usual. In TYPEAWAY, this is
  15854.  done near the beginning of the WM_CHAR message processing.
  15855.  
  15856.  Code Pages and Character Sets
  15857.  If you've had some earlier programming experience with the PC and you're
  15858.  familiar with the PC's extended character set, you may be wondering where
  15859.  some of these composite characters come from, because not all of them are
  15860.  supported by the PC character set.
  15861.  
  15862.  By default, the Presentation Manager does not use the PC character set for
  15863.  text written to the window using GPI functions. The "old PC" character set
  15864.  is codepage 437. The default codepage for GPI is called the "new PC"
  15865.  character set, and is codepage 850. In codepage 850 some of the line-drawing
  15866.  characters are replaced with composite characters.
  15867.  
  15868.  Under AVIO, the situation is a little different: AVIO will use the system
  15869.  default codepage (which is 437) unless the CONFIG.SYS file has a CODEPAGE
  15870.  statement. In this case, AVIO uses the first codepage in the CODEPAGE
  15871.  statement and can be switched to the other using the VioSetCp function. The
  15872.  OS/2 CHCP (change codepage) command affects the AVIO codepage but not the
  15873.  GPI codepage.
  15874.  
  15875.  If you need to convert a text string to upper case, do not use the C
  15876.  functions available for this purpose. These functions will work only with
  15877.  ASCII codes under 128. Instead, use the WinUpper and WinUpperChar functions
  15878.  and use WinCompareStrings for sorting.
  15879.  
  15880.  Reading Character Strings
  15881.  
  15882.  Because a program gets WM_CHAR messages one at a time, there doesn't seem to
  15883.  be anything in the Presentation Manager that corresponds to the KbdStringIn
  15884.  function to read an entire character string. In the Presentation Manager you
  15885.  do this a little differently. You create a child window control of the
  15886.  predefined WC_ENTRYFIELD class. This window accepts typed input, understands
  15887.  cursor movement keys, and can even scroll the input left and right if it's
  15888.  too long to fit in the window. We'll create such a child window control in
  15889.  Chapter 14.
  15890.  
  15891.  
  15892.  Breaking the Rules
  15893.  
  15894.  Presentation Manager programming often seems to involve so many rules that
  15895.  it can feel good to break a few. The final program in this chapter does just
  15896.  that. Earlier I warned you against using the scan code that accompanies the
  15897.  WM_CHAR message. For reasons I'll discuss shortly, this final program uses
  15898.  the scan code.
  15899.  
  15900.  The Presentation Manager programs we've written so far have used the
  15901.  hardware of the PC (such as the video display) only through the software
  15902.  interface provided by OS/2 and the Presentation Manager. But this program
  15903.  directly accesses the PC hardware to control the speaker. Perhaps even more
  15904.  shocking is the fact that part of the program is written in assembly
  15905.  language rather than C.
  15906.  
  15907.  Playing Music on the Keyboard
  15908.  
  15909.  The ORGAN program shown in Figure 8-6 lets you play your keyboard as if it
  15910.  were a 7-octave organ.
  15911.  
  15912.  Figure 8-6.  The ORGAN program.
  15913.  
  15914.    The ORGAN File
  15915.  
  15916.    #-----------------
  15917.    # ORGAN make file
  15918.    #-----------------
  15919.  
  15920.    organ.obj : organ.c organ.h
  15921.         cl -c -G2sw -W3 organ.c
  15922.  
  15923.    speaker.obj : speaker.asm
  15924.         masm speaker ;
  15925.  
  15926.    organ.exe : organ.obj speaker.obj organ.def
  15927.         link organ speaker, /align:16, NUL, os2, organ
  15928.  
  15929.    The ORGAN.C File
  15930.  
  15931.    /*--------------------------------------
  15932.       ORGAN.C --  Play Organ from Keyboard
  15933.      --------------------------------------*/
  15934.  
  15935.    #define INCL_DOS
  15936.    #define INCL_WIN
  15937.    #define INCL_GPI
  15938.    #include <os2.h>
  15939.    #include "organ.h"
  15940.  
  15941.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  15942.    VOID    EXPENTRY Speaker (USHORT usFreq) ;
  15943.  
  15944.    SHORT  xOffset, yOffset, cxCaps, cyChar ;
  15945.    USHORT usLastScan ;
  15946.  
  15947.    int main (void)
  15948.         {
  15949.         static CHAR  szClientClass [] = "Organ" ;
  15950.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  15951.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  15952.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  15953.         HAB          hab ;
  15954.         HMQ          hmq ;
  15955.         HWND         hwndFrame, hwndClient ;
  15956.         QMSG         qmsg ;
  15957.  
  15958.         if (DosPortAccess (0, 0, 0x42, 0x61))   // Don't run if port access fa
  15959.              return 1 ;
  15960.  
  15961.         hab = WinInitialize (0) ;
  15962.         hmq = WinCreateMsgQueue (hab, 0) ;
  15963.  
  15964.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  15965.  
  15966.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  15967.                                         &flFrameFlags, szClientClass, NULL,
  15968.                                         0L, NULL, 0, &hwndClient) ;
  15969.  
  15970.         WinSendMsg (hwndFrame, WM_SETICON,
  15971.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  15972.                     NULL) ;
  15973.  
  15974.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  15975.              WinDispatchMsg (hab, &qmsg) ;
  15976.  
  15977.         WinDestroyWindow (hwndFrame) ;
  15978.         WinDestroyMsgQueue (hmq) ;
  15979.         WinTerminate (hab) ;
  15980.         DosPortAccess (0, 1, 0x42, 0x61) ;
  15981.         return 0 ;
  15982.         }
  15983.  
  15984.    VOID DrawKey (HPS hps, USHORT usScanCode, BOOL fInvert)
  15985.         {
  15986.         RECTL rcl ;
  15987.  
  15988.         rcl.xLeft   = 3 * cxCaps * key[usScanCode].xPos / 2 + xOffset ;
  15989.         rcl.yBottom = 3 * cyChar * key[usScanCode].yPos / 2 + yOffset ;
  15990.  
  15991.         rcl.xRight  = rcl.xLeft   + 3 * cxCaps ;
  15992.         rcl.yTop    = rcl.yBottom + 3 * cyChar / 2 ;
  15993.  
  15994.         WinDrawText (hps, -1, key[usScanCode].szKey, &rcl,
  15995.                      CLR_NEUTRAL, CLR_BACKGROUND,
  15996.                      DT_CENTER | DT_VCENTER | DT_ERASERECT) ;
  15997.         if (fInvert)
  15998.              WinInvertRect (hps, &rcl) ;
  15999.  
  16000.         WinDrawBorder (hps, &rcl, 1, 1, CLR_NEUTRAL, CLR_BACKGROUND,
  16001.                        DB_STANDARD) ;
  16002.         }
  16003.  
  16004.    VOID ProcessKey (HPS hps, USHORT usScanCode, USHORT fsFlags)
  16005.         {
  16006.         static USHORT ausOctFreq [] = { 262, 277, 294, 311, 330, 349,
  16007.                                         370, 392, 415, 440, 466, 494 } ;
  16008.         USHORT        usOct, usFreq ;
  16009.  
  16010.         if (usScanCode >= NUMSCANS)                       // No scan codes ove
  16011.              return ;
  16012.         if ((usOct = key[usScanCode].sOctave) == -1)      // Non-music key
  16013.              return ;
  16014.  
  16015.         if (fsFlags & KC_KEYUP)                           // For key up
  16016.              {
  16017.              if (usLastScan == usScanCode)                // If that's the not
  16018.                   {
  16019.                   Speaker (0) ;                           // turn off speaker
  16020.                   DrawKey (hps, usScanCode, FALSE) ;      // and redraw key
  16021.                   usLastScan = 0 ;
  16022.                   }
  16023.              return ;
  16024.              }
  16025.         if (fsFlags & KC_PREVDOWN)                        // Ignore typematics
  16026.              return ;
  16027.  
  16028.         usFreq = ausOctFreq [key[usScanCode].sNote] ;     // Get frequency
  16029.  
  16030.         if (fsFlags & KC_SHIFT)
  16031.              usOct += fsFlags & KC_ALT ? 2 : 1 ;          // Higher octave
  16032.         else if (fsFlags & KC_CTRL)
  16033.              usOct -= fsFlags & KC_ALT ? 2 : 1 ;          // Lower octave
  16034.  
  16035.         if (usOct > 4)                                    // Shift frequency
  16036.              usFreq <<= (usOct - 4) ;                     //   for octave
  16037.         else if (usOct < 4)
  16038.              usFreq >>= (4 - usOct) ;
  16039.  
  16040.         Speaker (usFreq) ;                                // Turn on speaker
  16041.         DrawKey (hps, usScanCode, TRUE) ;                 // Draw the inverted
  16042.  
  16043.         if (usLastScan != 0)
  16044.              DrawKey (hps, usLastScan, FALSE) ;           // Redraw previous k
  16045.         usLastScan = usScanCode ;                         // Save scan code
  16046.         }
  16047.  
  16048.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  16049.         {
  16050.         FONTMETRICS fm ;
  16051.         HPS         hps ;
  16052.         SHORT       cxClient, cyClient ;
  16053.         USHORT      usScanCode ;
  16054.  
  16055.         switch (msg)
  16056.              {
  16057.              case WM_CREATE:
  16058.                   hps = WinGetPS (hwnd) ;
  16059.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  16060.                   cxCaps = (SHORT) fm.lEmInc ;
  16061.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  16062.                   WinReleasePS (hps) ;
  16063.                   return 0 ;
  16064.  
  16065.              case WM_SIZE:
  16066.                   cxClient = SHORT1FROMMP (mp2) ;
  16067.                   cyClient = SHORT2FROMMP (mp2) ;
  16068.  
  16069.                   xOffset = (cxClient - 25 * 3 * cxCaps / 2) / 2 ;
  16070.                   yOffset = (cyClient - 6 * cyChar) / 2 ;
  16071.                   return 0 ;
  16072.  
  16073.              case WM_CHAR:
  16074.                   if (!(CHARMSG(&msg)->fs & KC_SCANCODE))
  16075.                        break ;
  16076.  
  16077.                   hps = WinGetPS (hwnd) ;
  16078.                   ProcessKey (hps, CHARMSG(&msg)->scancode, CHARMSG(&msg)->fs)
  16079.                   WinReleasePS (hps) ;
  16080.                   return 0 ;
  16081.  
  16082.              case WM_PAINT:
  16083.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  16084.                   GpiErase (hps) ;
  16085.  
  16086.                   for (usScanCode = 0 ; usScanCode < NUMSCANS ; usScanCode++)
  16087.                        if (key[usScanCode].xPos != -1)
  16088.                             DrawKey (hps, usScanCode, usScanCode == usLastScan
  16089.  
  16090.                   WinEndPaint (hps) ;
  16091.                   return 0 ;
  16092.              }
  16093.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  16094.         }
  16095.  
  16096.    The ORGAN.H File
  16097.  
  16098.    /*---------------------
  16099.       ORGAN.H header file
  16100.      ---------------------*/
  16101.  
  16102.    #define NUMSCANS    (sizeof key / sizeof key[0])
  16103.  
  16104.    struct
  16105.         {
  16106.         SHORT sOctave ;
  16107.         SHORT sNote ;
  16108.         SHORT yPos ;
  16109.         SHORT xPos ;
  16110.         CHAR  *szKey ;
  16111.         }
  16112.         key [] =
  16113.         {
  16114.                                  // Scan  Char  Oct  Note
  16115.                                  // ----  ----  ---  ----
  16116.         -1, -1, -1, -1, NULL,    //   0   None
  16117.         -1, -1, -1, -1, NULL,    //   1   Esc
  16118.         -1, -1,  3,  0, "",      //   2     1
  16119.          4,  1,  3,  2, "C#",    //   3     2    4    C#
  16120.  
  16121.          4,  3,  3,  4, "D#",    //   4     3    4    D#
  16122.         -1, -1,  3,  6, "",      //   5     4
  16123.          4,  6,  3,  8, "F#",    //   6     5    4    F#
  16124.          4,  8,  3, 10, "G#",    //   7     6    4    G#
  16125.          4, 10,  3, 12, "A#",    //   8     7    4    A#
  16126.         -1, -1,  3, 14, "",      //   9     8
  16127.          5,  1,  3, 16, "C#",    //  10     9    5    C#
  16128.          5,  3,  3, 18, "D#",    //  11     0    5    D#
  16129.         -1, -1,  3, 20, "",      //  12     -
  16130.          5,  6,  3, 22, "F#",    //  13     =    5    F#
  16131.         -1, -1, -1, -1, NULL,    //  14    Back
  16132.  
  16133.         -1, -1, -1, -1, NULL,    //  15    Tab
  16134.          4,  0,  2,  1, "C",     //  16     q    4    C
  16135.          4,  2,  2,  3, "D",     //  17     w    4    D
  16136.          4,  4,  2,  5, "E",     //  18     e    4    E
  16137.          4,  5,  2,  7, "F",     //  19     r    4    F
  16138.          4,  7,  2,  9, "G",     //  20     t    4    G
  16139.          4,  9,  2, 11, "A",     //  21     y    4    A
  16140.          4, 11,  2, 13, "B",     //  22     u    4    B
  16141.          5,  0,  2, 15, "C",     //  23     i    5    C
  16142.          5,  2,  2, 17, "D",     //  24     o    5    D
  16143.          5,  4,  2, 19, "E",     //  25     p    5    E
  16144.          5,  5,  2, 21, "F",     //  26     [    5    F
  16145.          5,  7,  2, 23, "G",     //  27     ]    5    G
  16146.         -1, -1, -1, -1, NULL,    //  28    Ent
  16147.  
  16148.         -1, -1, -1, -1, NULL,    //  29    Ctrl
  16149.          2,  8,  1,  2, "G#",    //  30     a    2    G#
  16150.          2, 10,  1,  4, "A#",    //  31     s    2    A#
  16151.         -1, -1,  1,  6, "",      //  32     d
  16152.          3,  1,  1,  8, "C#",    //  33     f    3    C#
  16153.          3,  3,  1, 10, "D#",    //  34     g    3    D#
  16154.         -1, -1,  1, 12, "",      //  35     h
  16155.          3,  6,  1, 14, "F#",    //  36     j    3    F#
  16156.          3,  8,  1, 16, "G#",    //  37     k    3    G#
  16157.          3, 10,  1, 18, "A#",    //  38     l    3    A#
  16158.         -1, -1,  1, 20, "",      //  39     ;
  16159.          4,  1,  1, 22, "C#",    //  40     '    4    C#
  16160.         -1, -1, -1, -1, NULL,    //  41     '
  16161.  
  16162.         -1, -1, -1, -1, NULL,    //  42    Shift
  16163.         -1, -1, -1, -1, NULL,    //  43     \
  16164.          2,  9,  0,  3, "A",     //  44     z    2    A
  16165.  
  16166.          2, 11,  0,  5, "B",     //  45     x    2    B
  16167.          3,  0,  0,  7, "C",     //  46     c    3    C
  16168.          3,  2,  0,  9, "D",     //  47     v    3    D
  16169.          3,  4,  0, 11, "E",     //  48     b    3    E
  16170.          3,  5,  0, 13, "F",     //  49     n    3    F
  16171.          3,  7,  0, 15, "G",     //  50     m    3    G
  16172.          3,  9,  0, 17, "A",     //  51     ,    3    A
  16173.          3, 11,  0, 19, "B",     //  52     .    3    B
  16174.          4,  0,  0, 21, "C"      //  53     /    4    C
  16175.         } ;
  16176.  
  16177.    The SPEAKER.ASM File
  16178.  
  16179.    ;-------------------------------------------------
  16180.    ; SPEAKER.ASM -- Ring 2 routine for ORGAN program
  16181.    ;                (Accesses Intel 8255 Timer chip)
  16182.    ;-------------------------------------------------
  16183.  
  16184.                   PUBLIC    Speaker
  16185.                   .286
  16186.                   .MODEL    MEDIUM, PASCAL
  16187.  
  16188.                   .DATA
  16189.    ClockFreq      dd   1193180
  16190.  
  16191.                   .CODE     SPEAKER──TEXT
  16192.    Speaker        PROC FAR  Frequency:WORD
  16193.                   Cli                                ; Disable interrupts
  16194.  
  16195.                   Mov  BX, Frequency                 ; Get parameter from stac
  16196.                   Or   BX, BX                        ; Check if it's zero
  16197.                   Jz   TurnOff                       ; If so, turn off sound
  16198.  
  16199.                   Mov  AL, 10110110b                 ; Set flags for programmi
  16200.                   Out  43h, AL
  16201.  
  16202.                   Mov  AX, WORD PTR [ClockFreq]      ; Calculate timer frequen
  16203.                   Mov  DX, WORD PTR [ClockFreq + 2]
  16204.                   Div  BX
  16205.  
  16206.                   Out  42h, AL                       ; Output low byte
  16207.                   Jmp  $ + 2                         ; Delay
  16208.  
  16209.                   Mov  AL, AH                        ; Output high byte
  16210.                   Out  42h, AL
  16211.                   Jmp  $ + 2                         ; Delay
  16212.  
  16213.                   In   AL, 61h                       ; Get 8255 bits
  16214.                   Jmp  $ + 2                         ; Delay
  16215.  
  16216.                   Or   AL, 00000011b                 ; Set bits for speaker
  16217.                   Out  61h, AL                       ; Set 8255 bits
  16218.                   Jmp  Return
  16219.  
  16220.    TurnOff:       In   AL, 61h                       ; Get 8255 bits
  16221.                   Jmp  $ + 2                         ; Delay
  16222.                   And  AL, 11111101b                 ; Set bits for no speaker
  16223.                   Out  61h, AL                       ; Set 8255 bits
  16224.  
  16225.    Return:        Sti                                ; Enable interrupts
  16226.                   Ret
  16227.    Speaker        ENDP
  16228.                   END
  16229.  
  16230.  
  16231.    The ORGAN.DEF File
  16232.  
  16233.    ;----------------------------------
  16234.    ; ORGAN.DEF module definition file
  16235.    ;----------------------------------
  16236.  
  16237.    NAME           ORGAN     WINDOWAPI
  16238.  
  16239.    DESCRIPTION    'Play Organ from Keyboard (C) Charles Petzold, 1988'
  16240.    PROTMODE
  16241.    HEAPSIZE       1024
  16242.    STACKSIZE      8192
  16243.    SEGMENTS       SPEAKER_TEXT   IOPL
  16244.    EXPORTS        ClientWndProc
  16245.                   Speaker        1
  16246.  
  16247.  
  16248.  To run ORGAN, you'll need the following line in your CONFIG.SYS file:
  16249.  
  16250.    IOPL=YES
  16251.  
  16252.  If this line is not in CONFIG.SYS, edit the file to include the line and
  16253.  reboot.
  16254.  
  16255.  The program displays part of the keyboard in the window, as shown in Figure
  16256.  8-7.
  16257.  
  16258.  The keys are labeled with the notes they generate. When you press any key,
  16259.  the key is displayed in reverse video as the note is played. The note stops
  16260.  when you release the key. You can shift up one octave by pressing the Shift
  16261.  key before you press the note key, and you can shift up two octaves by
  16262.  pressing Shift and Alt together. Pressing Ctrl shifts down one octave; Ctrl
  16263.  and Alt shift down two octaves. Due to the limitations of the PC's sound
  16264.  generation hardware, you can play only one note at a time.
  16265.  
  16266.  When Scan Codes Are Important
  16267.  
  16268.  The ORGAN.H header file contains a structure called key that maps scan codes
  16269.  into note and octave combinations. The yPos and xPos fields of this
  16270.  structure are used within ORGAN.C to draw the keyboard on the screen.
  16271.  
  16272.  All the keys that play notes generate character codes, but I decided to use
  16273.  scan codes to allow ORGAN to be used with European keyboards. For example,
  16274.  the letter key in the lower-left corner of the keyboard is a Z on a U.S.
  16275.  keyboard but a Y on a German keyboard. Moreover, many of the symbol keys on
  16276.  foreign language keyboards are different than those on the U.S. keyboards.
  16277.  
  16278.  The scan codes, however, are the same for U.S. keyboards and foreign
  16279.  language keyboards. The scan code for the lower-left-letter key is always a
  16280.  44 regardless whether the key generates a Z or a Y. This allows ORGAN to be
  16281.  used on a wider variety of keyboards. Just don't expect to be able to simply
  16282.  recompile ORGAN for versions of the Presentation Manager that may someday
  16283.  run on non-PC hardware. ORGAN is very much dependent on the PC.
  16284.  
  16285.  Using IOPL Segments
  16286.  
  16287.  When you press a key in ORGAN, the note sounds and continues to sound until
  16288.  you release the key. The only mechanisms for generating sounds in OS/2 are
  16289.  the DosBeep function supported in the OS/2 kernel and the WinAlarm function.
  16290.  Both of these functions make a tone for a specified period of time.
  16291.  
  16292.  But in ORGAN the length of this tone depends on how long the key is pressed.
  16293.  Therefore, I had to use a different approach. This required writing a
  16294.  function that directly accesses the hardware of the PC's speaker. This
  16295.  function is called Speaker and is in the SPEAKER.ASM file. It takes one
  16296.  2-byte parameter, which is the frequency in Hertz. A zero parameter turns
  16297.  the speaker off.
  16298.  
  16299.  Under OS/2, functions that access the hardware of the PC must be placed in
  16300.  special segments called IOPL ("I/O privilege level") segments. These
  16301.  segments run in priority level 2 rather than the normal priority level 3.
  16302.  IOPL segments must be identified as such in the module definition file. This
  16303.  is indicated in the SEGMENTS statement:
  16304.  
  16305.    SEGMENTS  SPEAKER_TEXT  IOPL
  16306.  
  16307.  The Speaker function is in a segment called SPEAKER_TEXT, and the IOPL
  16308.  keyword tells LINK to flag this segment as an IOPL segment. The entry point
  16309.  to the Speaker function must also be listed in the EXPORTS section of the
  16310.  module definition file along with ClientWndProc:
  16311.  
  16312.    EXPORTS   ClientWndProc
  16313.              Speaker        1
  16314.  
  16315.  The 1 that follows the Speaker function name indicates the number of WORD
  16316.  parameters to the function.
  16317.  
  16318.  I wrote the Speaker function in assembly language because it is not normally
  16319.  possible to call functions located in a ring 3 segment from a function in a
  16320.  ring 2 segment. Because the rest of ORGAN runs in ring 3, the Speaker
  16321.  function can't make calls to C library functions. However, Speaker needs to
  16322.  make a 32-bit divide; if Speaker were written in C, that would require a C
  16323.  library function call.
  16324.  
  16325.  Otherwise, the program is fairly straightforward. The DrawKey function is
  16326.  responsible for drawing the keys on the window, and ProcessKey is
  16327.  responsible for determining what frequency to use for a particular key
  16328.  board message.
  16329.  
  16330.  Of course, you might not like the idea of directly accessing the PC hardware
  16331.  in a Presentation Manager program, and I tend to agree. But still, it's nice
  16332.  to know that if you want to, you can.
  16333.  
  16334.  
  16335.  Chapter 9  Taming the Mouse
  16336.  ───────────────────────────────────────────────────────────────────────────
  16337.  
  16338.  
  16339.  All user input to a Presentation Manager program comes from the keyboard and
  16340.  the mouse. The keyboard is adequate for alphanumeric input and rudimentary
  16341.  cursor movement. The mouse provides a more intimate connection between the
  16342.  user and the objects on the screen. As an extension of the user's fingers,
  16343.  the mouse can point, grab, and move. As you've seen in the sample programs
  16344.  from previous chapters, the Presentation Manager takes care of all mouse
  16345.  input involving menus, scroll bars, and the moving and sizing of windows.
  16346.  Your programs will be concerned mostly with mouse activity that occurs
  16347.  within the client window.
  16348.  
  16349.  When you program for the Presentation Manager, however, the mouse must be
  16350.  viewed as secondary to the keyboard──the Presentation Manager doesn't
  16351.  require a mouse. Obviously, some programs (drawing programs and
  16352.  page-composition programs) become awkward when controlled solely from the
  16353.  keyboard, so in those cases you might feel justified in not providing a
  16354.  keyboard interface that duplicates all the mouse functions. That's up to
  16355.  you. Just be aware that if your program requires a mouse, it won't be usable
  16356.  by all Presentation Manager users.
  16357.  
  16358.  The Presentation Manager supports a mouse that has one, two, or three
  16359.  buttons. You must decide how many mouse buttons you'll use in your program.
  16360.  The easiest approach is to go for the lowest common denominator and use only
  16361.  one mouse button.
  16362.  
  16363.  
  16364.  Mouse and Pointer Basics
  16365.  
  16366.  Let's begin with a few simple definitions, starting with the distinction
  16367.  between the mouse and the pointer. The mouse is the object that sits on the
  16368.  desk. The pointer is a small bitmapped picture on the screen. When you move
  16369.  the mouse with your hand, the Presentation Manager moves the pointer.
  16370.  
  16371.  Clicking the mouse is pressing and releasing a mouse button. Double-clicking
  16372.  is pressing and releasing the mouse button twice in succession. To qualify
  16373.  as a double click, both clicks must occur within a fixed period of time (by
  16374.  default, half a second) and with the pointer in approximately the same area
  16375.  of the screen (within an area of about half a system font character).
  16376.  Dragging the mouse is holding down the mouse button and moving the mouse.
  16377.  For example, you drag the mouse when you change the position or size of a
  16378.  window.
  16379.  
  16380.  More About the Pointer
  16381.  
  16382.  The Presentation Manager moves the pointer in response to mouse movements.
  16383.  The Presentation Manager includes several predefined pointer shapes, with
  16384.  the most familiar being the arrow pointer used by default on most windows.
  16385.  Four other predefined pointers (double-headed arrows of various types) are
  16386.  used on the sizing border. You can also create your own customized pointers,
  16387.  as described in Chapter 12.
  16388.  
  16389.  The displayed size of the mouse pointer is dependent on the resolution of
  16390.  the video display. For example, on the IBM EGA a pointer is 32 pixels wide
  16391.  and 32 pixels high. On an IBM CGA a pointer is only 16 pixels high because
  16392.  the vertical resolution is lower. (A program can obtain the dimensions of
  16393.  the pointer from WinQuerySysValue using the SV_CXPOINTER and SV_CYPOINTER
  16394.  parameters.) Every pointer has a "hot spot," which is a single pixel
  16395.  position within the pointer bitmap. For the standard arrow pointer, the hot
  16396.  spot is the tip of the arrow. The Presentation Manager uses the hot spot as
  16397.  the position of the pointer.
  16398.  
  16399.  The Pointer Position
  16400.  Programs that use the mouse for input must often determine the position of
  16401.  the pointer or, more precisely, the coordinates of the pointer's hot spot.
  16402.  Such programs can make this determination in three ways: by calling
  16403.  WinQueryPointerPos; by calling WinQueryMsgPos; or by processing WM_MOUSEMOVE
  16404.  messages.
  16405.  
  16406.  The WinQueryPointerPos Function
  16407.  This function fills in the x and y fields of the POINTL structure with the
  16408.  current pointer position in screen coordinates, relative to the lower-left
  16409.  corner of the screen:
  16410.  
  16411.    WinQueryPointerPos (HWND_DESKTOP, &ptl) ;
  16412.  
  16413.  You can call this function at any time.
  16414.  
  16415.  The WinQueryMsgPos Function
  16416.  You can use the second method, the WinQueryMsgPos function, while processing
  16417.  a message in a window procedure. This function reports the screen
  16418.  coordinates of the pointer at the time a message was last placed in the
  16419.  program's message queue:
  16420.  
  16421.    WinQueryMsgPos (hab, &ptl) ;
  16422.  
  16423.  If the window procedure calls this function while processing a nonqueued
  16424.  message, this pointer position could be long out of date. The pointer
  16425.  position obtained from WinQueryMsgPos is originally part of the QMSG
  16426.  structure that the Presentation Manager fills in when you retrieve a message
  16427.  from the message queue with WinGetMsg. However, the pointer position isn't
  16428.  passed to the window procedure along with the more important QMSG fields
  16429.  (the window handle, message number, mp1, and mp2). You use WinQueryMsgPos to
  16430.  get this field. This function is sometimes useful when you need to determine
  16431.  the pointer position at the time a key on the key board was pressed.
  16432.  
  16433.  Both WinQueryPointerPos and WinQueryMsgPos return the pointer coordinates
  16434.  relative to the lower-left corner of the screen, but the functions don't
  16435.  necessarily return the same value. WinQueryPointerPos returns the pointer
  16436.  position at the time the function is called, whereas WinQueryMsgPos returns
  16437.  the position at the time the message currently being processed was posted in
  16438.  the message queue.
  16439.  
  16440.  Processing the WM_MOUSEMOVE Message
  16441.  The third way to obtain the pointer position is by processing the
  16442.  WM_MOUSEMOVE message in the window procedure. The pointer coordinates are
  16443.  stored in mp1. You can extract the x (horizontal) coordinate with the
  16444.  expression
  16445.  
  16446.    xPointer = SHORT1FROMMP (mp1) ;
  16447.  
  16448.  and extract the y (vertical) coordinate using
  16449.  
  16450.    yPointer = SHORT2FROMMP (mp1) ;
  16451.  
  16452.  ───────────────────────────────────────────────────────────────────────────
  16453.  NOTE:
  16454.     The PMWIN.H header file also includes a MOUSEMSG macro that is similar to
  16455.     the CHARMSG macro discussed in Chapter 8. You can use MOUSEMSG to obtain
  16456.     the pointer position like this:
  16457.  
  16458.    case WM_MOUSEMOVE:
  16459.         xPointer = MOUSEMSG (&msg) -> x ;
  16460.         yPointer = MOUSEMSG (&msg) -> y ;
  16461.  ────────────────────────────────────────────────────────────────────────────
  16462.  
  16463.  Unlike the pointer position obtained from WinQueryPointerPos and
  16464.  WinQueryMsgPos, the pointer position in the WM_MOUSEMOVE message is in
  16465.  window coordinates relative to the lower-left corner of the window receiving
  16466.  the message. Under normal circumstances, a window procedure receives
  16467.  WM_MOUSEMOVE messages only when the pointer is positioned over the window.
  16468.  Thus the coordinates in mp1 won't be negative. (The exception is when a
  16469.  program "captures the mouse," a technique I'll discuss later in this
  16470.  chapter.)
  16471.  
  16472.  You'll recall from Chapter 8 that a window procedure receives WM_CHAR
  16473.  messages when the window has the input focus. The mouse is handled
  16474.  differently──a window procedure receives WM_MOUSEMOVE messages when the
  16475.  pointer is positioned over the window, regardless of the active window and
  16476.  the focus window. If the mouse pointer is positioned over overlapping
  16477.  windows, the topmost window receives the WM_MOUSEMOVE message.
  16478.  
  16479.  Processing WM_MOUSEMOVE messages is generally the easiest way for a program
  16480.  to determine the pointer position, for two reasons:
  16481.  
  16482.    ■  The message notifies a window procedure when the mouse has moved.
  16483.  
  16484.    ■  The coordinates of the pointer position are relative to the window
  16485.       rather than the screen.
  16486.  
  16487.  WM_MOUSEMOVE Message Default Processing
  16488.  After processing most messages, the window procedure returns a 0. Any
  16489.  message that a window procedure does not process must be passed to WinDef
  16490.  WindowProc for default processing.
  16491.  
  16492.  But WM_MOUSEMOVE messages should be handled a little differently. The
  16493.  Presentation Manager documentation recommends that a window procedure return
  16494.  1 if it processes a WM_MOUSEMOVE message and 0 if it does not. But this is
  16495.  just a convention. The value you return from the window procedure is not
  16496.  used for anything important──it is simply returned from the WinDispatchMsg
  16497.  call that originally dispatched the WM_MOUSEMOVE message to the window
  16498.  procedure.
  16499.  
  16500.  Rather than return a 0 or 1 from the window procedure, you'll probably want
  16501.  to conclude your WM_MOUSEMOVE processing with a break statement. This will
  16502.  cause WinDefWindowProc to be called for the same message. WinDefWindowProc
  16503.  processes WM_MOUSEMOVE messages by setting the pointer shape to the default
  16504.  tilted arrow.
  16505.  
  16506.  If you had the source code to WinDefWindowProc, you'd find that it looked
  16507.  something like this:
  16508.  
  16509.    MRESULT APIENTRY WinDefWindowProc (HPS hps, USHORT msg, MPARAM mp1, MPARAM
  16510.         {
  16511.  
  16512.              ∙
  16513.              ∙
  16514.              ∙
  16515.  
  16516.         switch (msg)
  16517.              {
  16518.  
  16519.                        ∙
  16520.                        ∙
  16521.                        ∙
  16522.  
  16523.              case WM_MOUSEMOVE:
  16524.                   WinSetPointer (HWND_DESKTOP,
  16525.                        WinQuerySysPointer (HWND_DESKTOP, SPTR_ARROW, FALSE)) ;
  16526.                   return 0 ;
  16527.  
  16528.                        ∙
  16529.                        ∙
  16530.                        ∙
  16531.  
  16532.              }
  16533.         return 0 ;
  16534.         }
  16535.  
  16536.  The WinQuerySysPointer function returns a handle to a system pointer. The
  16537.  SPTR_ARROW identifier refers to the tilted arrow pointer. The WinSetPointer
  16538.  call uses that pointer handle to set the pointer shape.
  16539.  
  16540.  If you want a different pointer shape when the pointer is positioned on your
  16541.  client window, you can call WinSetPointer while you are processing
  16542.  WM_MOUSEMOVE and return from the window procedure without calling WinDef
  16543.  WindowProc. You can set the pointer to any of the system pointers (obtained
  16544.  from WinQuerySysPointer using the SPTR identifiers) or to a customized
  16545.  pointer (discussed in Chapter 12).
  16546.  
  16547.  If you do not call WinSetPointer while processing the WM_MOUSEMOVE message,
  16548.  you should call WinDefWindowProc so the tilted arrow pointer is set.
  16549.  Otherwise, you may find that the pointer used by another window (for
  16550.  example, the double-headed arrows used by the sizing border window)
  16551.  continues to be used when the pointer is inside the client window.
  16552.  
  16553.  Processing WM_MOUSEMOVE Messages
  16554.  
  16555.  The WEB program, shown in Figure 9-1, processes WM_MOUSEMOVE messages.
  16556.  Whenever this program receives a WM_MOUSEMOVE message, it draws a series of
  16557.  lines from the pointer position encoded in mp1 to the four corners and four
  16558.  sides of the client window. The pattern looks like a web (Figure 9-2). As
  16559.  you move the mouse around the window, the center of the web follows. When
  16560.  you move the mouse outside the client window, the client window stops
  16561.  receiving WM_MOUSEMOVE messages; thus the web stops changing shape.
  16562.  
  16563.  Figure 9-1.  The WEB program.
  16564.  
  16565.    The WEB File
  16566.  
  16567.    #---------------
  16568.    # WEB make file
  16569.    #---------------
  16570.  
  16571.    web.obj : web.c
  16572.         cl -c -G2sw -W3 web.c
  16573.  
  16574.    web.exe : web.obj web.def
  16575.         link web, /align:16, NUL, os2, web
  16576.  
  16577.    The WEB.C File
  16578.  
  16579.    /*--------------------------------------
  16580.       WEB.C -- Mouse Movement Demo Program
  16581.      --------------------------------------*/
  16582.  
  16583.    #define INCL_WIN
  16584.    #define INCL_GPI
  16585.    #include <os2.h>
  16586.  
  16587.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  16588.  
  16589.    int main (void)
  16590.         {
  16591.         static CHAR  szClientClass [] = "Web" ;
  16592.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  16593.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  16594.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  16595.  
  16596.         HAB          hab ;
  16597.         HMQ          hmq ;
  16598.         HWND         hwndFrame, hwndClient ;
  16599.         QMSG         qmsg ;
  16600.  
  16601.         hab = WinInitialize (0) ;
  16602.         hmq = WinCreateMsgQueue (hab, 0) ;
  16603.  
  16604.         WinRegisterClass (hab, szClientClass, ClientWndProc,
  16605.                           CS_SIZEREDRAW | CS_SYNCPAINT, 0) ;
  16606.  
  16607.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  16608.                                         &flFrameFlags, szClientClass, NULL,
  16609.                                         0L, NULL, 0, &hwndClient) ;
  16610.  
  16611.         WinSendMsg (hwndFrame, WM_SETICON,
  16612.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  16613.                     NULL) ;
  16614.  
  16615.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  16616.              WinDispatchMsg (hab, &qmsg) ;
  16617.  
  16618.         WinDestroyWindow (hwndFrame) ;
  16619.         WinDestroyMsgQueue (hmq) ;
  16620.         WinTerminate (hab) ;
  16621.         return 0 ;
  16622.         }
  16623.  
  16624.    VOID DrawWeb (HPS hps, POINTL *pptlPointerPos, POINTL *pptlClient)
  16625.         {
  16626.         POINTL ptl ;
  16627.                                       // Lower Left --> Pointer --> Upper Righ
  16628.         ptl.x = 0 ;
  16629.         ptl.y = 0 ;
  16630.         GpiMove (hps, &ptl) ;
  16631.         GpiLine (hps, pptlPointerPos) ;
  16632.         GpiLine (hps, pptlClient) ;
  16633.                                       // Upper Left --> Pointer --> Lower Righ
  16634.         ptl.x = 0 ;
  16635.         ptl.y = pptlClient->y ;
  16636.         GpiMove (hps, &ptl) ;
  16637.         GpiLine (hps, pptlPointerPos) ;
  16638.  
  16639.         ptl.x = pptlClient->x ;
  16640.         ptl.y = 0 ;
  16641.         GpiLine (hps, &ptl) ;
  16642.                                       // Lower Center --> Pointer --> Upper Ce
  16643.  
  16644.         ptl.x = pptlClient->x / 2 ;
  16645.         ptl.y = 0 ;
  16646.         GpiMove (hps, &ptl) ;
  16647.         GpiLine (hps, pptlPointerPos) ;
  16648.  
  16649.         ptl.y = pptlClient->y ;
  16650.         GpiLine (hps, &ptl) ;
  16651.                                       // Left Center --> Pointer --> Right Cen
  16652.         ptl.x = 0 ;
  16653.         ptl.y = pptlClient->y / 2 ;
  16654.         GpiMove (hps, &ptl) ;
  16655.         GpiLine (hps, pptlPointerPos) ;
  16656.  
  16657.         ptl.x = pptlClient->x ;
  16658.         GpiLine (hps, &ptl) ;
  16659.         }
  16660.  
  16661.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  16662.         {
  16663.         static POINTL ptlClient, ptlPointerPos ;
  16664.         HPS           hps ;
  16665.  
  16666.         switch (msg)
  16667.              {
  16668.              case WM_SIZE:
  16669.                   ptlClient.x = SHORT1FROMMP (mp2) ;
  16670.                   ptlClient.y = SHORT2FROMMP (mp2) ;
  16671.                   return 0 ;
  16672.  
  16673.              case WM_MOUSEMOVE:
  16674.                   hps = WinGetPS (hwnd) ;
  16675.                   GpiSetMix (hps, FM_INVERT) ;
  16676.  
  16677.                   DrawWeb (hps, &ptlPointerPos, &ptlClient) ;
  16678.  
  16679.                   ptlPointerPos.x = MOUSEMSG(&msg)->x ;
  16680.                   ptlPointerPos.y = MOUSEMSG(&msg)->y ;
  16681.  
  16682.                   DrawWeb (hps, &ptlPointerPos, &ptlClient) ;
  16683.  
  16684.                   WinReleasePS (hps) ;
  16685.                   break ;                       // do default processing
  16686.  
  16687.              case WM_PAINT:
  16688.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  16689.                   GpiErase (hps) ;
  16690.                   GpiSetMix (hps, FM_INVERT) ;
  16691.  
  16692.                   DrawWeb (hps, &ptlPointerPos, &ptlClient) ;
  16693.  
  16694.                   WinEndPaint (hps) ;
  16695.                   return 0 ;
  16696.              }
  16697.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  16698.         }
  16699.  
  16700.    The WEB.DEF File
  16701.  
  16702.    ;--------------------------------
  16703.    ; WEB.DEF module definition file
  16704.    ;--------------------------------
  16705.  
  16706.    NAME           WEB  WINDOWAPI
  16707.  
  16708.    DESCRIPTION    'Mouse Movement Demo Program (C) Charles Petzold, 1988'
  16709.    PROTMODE
  16710.    HEAPSIZE       1024
  16711.    STACKSIZE      8192
  16712.    EXPORTS        ClientWndProc
  16713.  
  16714.  
  16715.  Erasing and Redrawing
  16716.  
  16717.  WEB draws lines using a series of GpiMove and GpiLine calls in the DrawWeb
  16718.  function. When WEB receives a WM_MOUSEMOVE message, it must erase the lines
  16719.  previously drawn and draw new lines based on the new mouse position. One way
  16720.  to erase the old lines is to write over them using a different mix mode. The
  16721.  default mix mode, called FM_OVERPAINT, causes any object you draw to
  16722.  overpaint whatever was previously in the client window. You can instead set
  16723.  a mix mode of FM_INVERT:
  16724.  
  16725.    GpiSetMix (hps, FM_INVERT) ;
  16726.  
  16727.  This causes the Presentation Manager to draw the lines by inverting the
  16728.  color of the client window background. Thus if you set the mix mode to
  16729.  FM_INVERT and draw over the old lines, the lines are effectively erased.
  16730.  
  16731.  The WM_MOUSEMOVE code first sets the mix mode to FM_INVERT and then calls
  16732.  DrawWeb to erase the previous web:
  16733.  
  16734.    case WM_MOUSEMOVE:
  16735.         hps = WinGetPS (hwnd) ;
  16736.         GpiSetMix (hps, FM_INVERT) ;
  16737.  
  16738.         DrawWeb (hps, &ptlPointerPos, &ptlClient) ;
  16739.  
  16740.  The new position of the pointer is stored in ptlPointerPos, and the program
  16741.  calls DrawWeb again to draw the new web:
  16742.  
  16743.         ptlPointerPos.x = MOUSEMSG (&msg) -> x ;
  16744.         ptlPointerPos.y = MOUSEMSG (&msg) -> y ;
  16745.  
  16746.         DrawWeb (hps, &ptlPointerPos, &ptlClient) ;
  16747.  
  16748.         WinReleasePS (hps) ;
  16749.         break ;
  16750.  
  16751.  But there's a bug in this web. Both WM_MOUSEMOVE and WM_PAINT are queued
  16752.  messages. The WM_PAINT message is a low-priority message. If both a
  16753.  WM_MOUSEMOVE message and a WM_PAINT message are in the message queue,
  16754.  WM_MOUSEMOVE is retrieved first.
  16755.  
  16756.  If the pointer is positioned over a window when the window is first created,
  16757.  the Presentation Manager places an initial WM_MOUSEMOVE message in the
  16758.  program's message queue even if the mouse isn't moving during that time.
  16759.  This is often the first queued message the window procedure receives. The
  16760.  WM_PAINT message is usually the second queued message. But the code in WEB
  16761.  assumes that the window procedure receives a WM_PAINT message (and draws an
  16762.  initial web) before the first WM_MOUSEMOVE message (which begins by erasing
  16763.  the previous web). The fix for this is relatively easy. The window class is
  16764.  given a CS_SYNCPAINT style:
  16765.  
  16766.    WinRegisterClass (hab, szClientClass, ClientWndProc,
  16767.                     CS_SIZEREDRAW | CS_SYNCPAINT, 0) ;
  16768.  
  16769.  This class style makes WM_PAINT messages nonqueued. The window procedure
  16770.  receives a WM_PAINT message immediately whenever part of the window is
  16771.  invalid. The first WM_PAINT message then precedes the first WM_MOUSEMOVE
  16772.  message.
  16773.  
  16774.  If you move the mouse quickly within WEB's client area, you'll notice a lag
  16775.  between the position of the pointer and the center of the web. This results
  16776.  from the delay between the time the WM_MOUSEMOVE message is posted and the
  16777.  time it is actually processed. You can force the web to follow the movement
  16778.  of the mouse more closely by replacing the following two statements:
  16779.  
  16780.    ptlPointerPos.x = MOUSEMSG (&msg) -> x ;
  16781.    ptlPointerPos.y = MOUSEMSG (&msg) -> y ;
  16782.  
  16783.  with the following code:
  16784.  
  16785.    WinQueryPointerPos (HWND_DESKTOP, &ptlPointerPos) ;
  16786.    WinMapWindowPoints (HWND_DESKTOP, hwnd, &ptlPointerPos, 1) ;
  16787.  
  16788.  The WM_MOUSEMOVE message continues to notify the window procedure of a
  16789.  change in the pointer position, but the program obtains a more up-to-date
  16790.  pointer position from the WinQueryPointerPos function. The position must be
  16791.  converted from screen coordinates to client window coordinates with
  16792.  WinMapWindowPoints.
  16793.  
  16794.  You'll notice with both methods that no matter how quickly you move the
  16795.  mouse around the client window, the program never has to "catch up" to a
  16796.  stream of unprocessed WM_MOUSEMOVE messages. That's because the Presentation
  16797.  Manager doesn't fill up your message queue with a lot of WM_MOUSEMOVE
  16798.  messages──it posts them only as quickly as you can process them. If the
  16799.  queue already contains a WM_MOUSEMOVE message when a new WM_MOUSEMOVE
  16800.  message is ready, the Presentation Manager replaces the message currently in
  16801.  the queue. Therefore, only one WM_MOUSEMOVE message exists at a time in the
  16802.  message queue.
  16803.  
  16804.  
  16805.  Mouse Buttons and Hit-Testing
  16806.  
  16807.  A mouse can have one, two, or three buttons. A program can obtain the number
  16808.  of mouse buttons from WinQuerySysValue:
  16809.  
  16810.    lNumButtons = WinQuerySysValue (HWND_DESKTOP, SV_CMOUSEBUTTONS) ;
  16811.  
  16812.  If the return value is 0, no mouse is installed. A program can check for the
  16813.  presence of a mouse in this manner or by calling WinQuerySysValue with the
  16814.  SV_MOUSEPRESENT parameter.
  16815.  
  16816.  The PMWIN.H header file defines several identifiers you use in
  16817.  button-related functions and messages. These identifiers contain the words
  16818.  BUTTON1, BUTTON2, and BUTTON3 to refer to the three buttons. The following
  16819.  table shows how these identifiers normally correspond to the actual buttons
  16820.  on the mouse:
  16821.  
  16822.     Number ofIdentifier    BUTTON2       BUTTON3
  16823.     Mouse Buttons BUTTON1
  16824.     One           Center         ──            ──
  16825.     Two           Left          Right          ──
  16826.     Three         Left          Center        Right
  16827.  
  16828.  You can write your programs for a three-button mouse and then include
  16829.  special logic to mimic the third button for a two-button mouse and the
  16830.  second and third buttons for a one-button mouse. But the easiest approach is
  16831.  to assume that the mouse has only one button and to work entirely with the
  16832.  functions and messages that pertain to BUTTON1. The Presentation Manager's
  16833.  own window procedures for the menu, scroll bar, sizing border, title bar,
  16834.  push buttons, and so forth all work this way.
  16835.  
  16836.  Left-handed users often prefer to use their index finger for the first
  16837.  button. For this reason, the Presentation Manager Control Panel allows the
  16838.  user to switch the orientation of the buttons, like this:
  16839.  
  16840.     Number of     Identifier    BUTTON2       BUTTON3
  16841.     Mouse Buttons BUTTON1
  16842.     One           Center         ──            ──
  16843.     Two           Right         Left           ──
  16844.     Three         Right         Center        Left
  16845.  
  16846.  This swapping of the mouse buttons is invisible to your program; you needn't
  16847.  worry about it. The user knows which physical button is the first button,
  16848.  and that's all that's important. (But if you're writing a training program
  16849.  that draws a mouse on the screen and labels the buttons, you can determine
  16850.  if the mouse buttons have been swapped by calling WinQuerySysValue with the
  16851.  SV_SWAPBUTTON parameter.)
  16852.  
  16853.  A program can determine whether a mouse button is currently pressed or
  16854.  released by calling WinGetKeyState, the function used in Chapter 8 to
  16855.  determine the state of keys on the keyboard:
  16856.  
  16857.    sKeyState = WinGetKeyState (HWND_DESKTOP, VK_BUTTON1) ;
  16858.  
  16859.  The high bit of sKeyState is set (sKeyState is negative) if the first mouse
  16860.  button is currently down. You can use the VK_BUTTON2 and VK_BUTTON3
  16861.  identifiers to determine the state of the second and third buttons.
  16862.  
  16863.  Button Messages
  16864.  
  16865.  A window procedure is notified of button presses and releases by messages:
  16866.  
  16867.     Button    Pressed                 Released
  16868.     1         WM_BUTTON1DOWN          WM_BUTTON1UP
  16869.     2         WM_BUTTON2DOWN          WM_BUTTON2UP
  16870.     3         WM_BUTTON3DOWN          WM_BUTTON3UP
  16871.  
  16872.  If the user presses and releases the mouse button twice to qualify as a
  16873.  double click, the window procedure receives the two messages shown above for
  16874.  the first click and the following pair of messages for the second click:
  16875.  
  16876.     Button    Pressed Again           Released
  16877.     1         WM_BUTTON1DBLCLK        WM_BUTTON1UP
  16878.     2         WM_BUTTON2DBLCLK        WM_BUTTON2UP
  16879.     3         WM_BUTTON3DBLCLK        WM_BUTTON3UP
  16880.  
  16881.  The Presentation Manager routes these messages to window procedures in the
  16882.  same way it routes the WM_MOUSEMOVE message: The window underneath the
  16883.  pointer at the time of the button action determines the window procedure
  16884.  that receives the message. The pointer position is stored in the mp1
  16885.  parameter, just as it is in the WM_MOUSEMOVE message.
  16886.  
  16887.  WinDefWindowProc performs some important default processing of button down
  16888.  messages:
  16889.  
  16890.    case WM_BUTTON1DOWN:
  16891.    case WM_BUTTON2DOWN:
  16892.    case WM_BUTTON3DOWN:
  16893.         WinSetActiveWindow (HWND_DESKTOP, hwnd) ;
  16894.  
  16895.         hwndOwner = WinQueryWindow (hwnd, QW_OWNER, FALSE) ;
  16896.  
  16897.         if (hwndOwner != NULL)
  16898.              return WinSendMsg (hwndOwner, msg, mp1, mp2) ;
  16899.         else
  16900.              return 0 ;
  16901.  
  16902.  The WinSetActiveWindow call sets the active window to hwnd. If hwnd is not a
  16903.  top-level window, then hwnd is a descendant of a top-level window and that
  16904.  top-level window becomes active. This allows the user to bring a window to
  16905.  the foreground by clicking the client area with the mouse. You should either
  16906.  include a call to WinSetActiveWindow in your button down processing or call
  16907.  WinDefWindowProc.
  16908.  
  16909.  WinDefWindowProc also sends the message to the window's owner, under the
  16910.  assumption that, if the window is not interested in the message, the
  16911.  window's owner might be.
  16912.  
  16913.  Hit-Testing
  16914.  
  16915.  When you draw graphic figures or text on the screen, you determine the
  16916.  coordinates of each object (whether figure or text) and call the appropriate
  16917.  GPI functions to draw it. Often a program uses a mouse interface to allow a
  16918.  user to point to and manipulate these graphic objects. But that means your
  16919.  program must work backward from the pointer coordinates to determine which
  16920.  of these objects the mouse is pointing to. This process is called
  16921.  "hit-testing." Hit-testing can be complex, particularly if your client
  16922.  window contains figures that overlap or contains text in a variable-pitch
  16923.  font. To help out, GPI includes a built-in facility to draw a series of
  16924.  objects and then determine which object coincides with a particular point.
  16925.  You'll want to use this facility for complex hit-testing, but for simple
  16926.  hit-testing, you can use the old-fashioned techniques, which I'll discuss in
  16927.  this section.
  16928.  
  16929.  Simple Hit-Testing
  16930.  
  16931.  The CHECKER1 program, shown in Figure 9-3, demonstrates some simple
  16932.  hit-testing logic. The program draws 25 rectangles in a 5-by-5 grid. When
  16933.  you click within one of these rectangles, CHECKER1 draws an X in the
  16934.  rectangle. When you click again, the X disappears.
  16935.  
  16936.  Figure 9-3.  The CHECKER1 program.
  16937.  
  16938.    The CHECKER1 File
  16939.  
  16940.    #--------------------
  16941.    # CHECKER1 make file
  16942.    #--------------------
  16943.  
  16944.    checker1.obj : checker1.c
  16945.         cl -c -G2sw -W3 checker1.c
  16946.  
  16947.    checker1.exe : checker1.obj checker1.def
  16948.         link checker1, /align:16, NUL, os2, checker1
  16949.  
  16950.    The CHECKER1.C File
  16951.  
  16952.    /*-------------------------------------------
  16953.       CHECKER1.C -- Mouse Hit-Test Demo Program
  16954.      -------------------------------------------*/
  16955.  
  16956.    #define INCL_WIN
  16957.    #include <os2.h>
  16958.  
  16959.    #define DIVISIONS 5
  16960.  
  16961.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  16962.  
  16963.    int main (void)
  16964.         {
  16965.         static CHAR  szClientClass [] = "Checker1" ;
  16966.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  16967.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  16968.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  16969.         HAB          hab ;
  16970.         HMQ          hmq ;
  16971.         HWND         hwndFrame, hwndClient ;
  16972.         QMSG         qmsg ;
  16973.  
  16974.         hab = WinInitialize (0) ;
  16975.         hmq = WinCreateMsgQueue (hab, 0) ;
  16976.  
  16977.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  16978.  
  16979.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  16980.                                         &flFrameFlags, szClientClass, NULL,
  16981.                                         0L, NULL, 0, &hwndClient) ;
  16982.  
  16983.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  16984.              WinDispatchMsg (hab, &qmsg) ;
  16985.  
  16986.         WinDestroyWindow (hwndFrame) ;
  16987.         WinDestroyMsgQueue (hmq) ;
  16988.         WinTerminate (hab) ;
  16989.         return 0 ;
  16990.         }
  16991.  
  16992.    VOID DrawLine (HPS hps, LONG x1, LONG y1, LONG x2, LONG y2)
  16993.         {
  16994.         POINTL ptl ;
  16995.  
  16996.         ptl.x = x1 ;  ptl.y = y1 ;  GpiMove (hps, &ptl) ;
  16997.         ptl.x = x2 ;  ptl.y = y2 ;  GpiLine (hps, &ptl) ;
  16998.         }
  16999.  
  17000.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  17001.         {
  17002.         static BOOL  fBlockState [DIVISIONS] [DIVISIONS] ;
  17003.         static SHORT xBlock, yBlock ;
  17004.         HPS          hps ;
  17005.         RECTL        rcl ;
  17006.         SHORT        x, y ;
  17007.  
  17008.         switch (msg)
  17009.              {
  17010.              case WM_SIZE:
  17011.                   xBlock = SHORT1FROMMP (mp2) / DIVISIONS ;
  17012.                   yBlock = SHORT2FROMMP (mp2) / DIVISIONS ;
  17013.                   return 0 ;
  17014.  
  17015.              case WM_BUTTON1DOWN:
  17016.              case WM_BUTTON1DBLCLK:
  17017.                   if (xBlock > 0 && yBlock > 0)
  17018.                        {
  17019.                        x = MOUSEMSG(&msg)->x / xBlock ;
  17020.                        y = MOUSEMSG(&msg)->y / yBlock ;
  17021.  
  17022.                        if (x < DIVISIONS && y < DIVISIONS)
  17023.                             {
  17024.                             fBlockState [x][y] = !fBlockState [x][y] ;
  17025.  
  17026.                             rcl.xRight = xBlock + (rcl.xLeft   = x * xBlock) ;
  17027.                             rcl.yTop   = yBlock + (rcl.yBottom = y * yBlock) ;
  17028.  
  17029.                             WinInvalidateRect (hwnd, &rcl, FALSE) ;
  17030.                             }
  17031.                        else
  17032.                             WinAlarm (HWND_DESKTOP, WA_WARNING) ;
  17033.                        }
  17034.                   else
  17035.                        WinAlarm (HWND_DESKTOP, WA_WARNING) ;
  17036.  
  17037.                   break ;                       // do default processing
  17038.  
  17039.              case WM_PAINT:
  17040.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  17041.                   GpiErase (hps) ;
  17042.  
  17043.                   if (xBlock > 0 && yBlock > 0)
  17044.                        for (x = 0 ; x < DIVISIONS ; x++)
  17045.                             for (y = 0 ; y < DIVISIONS ; y++)
  17046.                                  {
  17047.                                  rcl.xRight = xBlock + (rcl.xLeft   = x * xBlo
  17048.                                  rcl.yTop   = yBlock + (rcl.yBottom = y * yBlo
  17049.  
  17050.                                  WinDrawBorder (hps, &rcl, 1, 1,
  17051.                                                 CLR_NEUTRAL, CLR_BACKGROUND,
  17052.                                                 DB_STANDARD | DB_INTERIOR) ;
  17053.  
  17054.                                  if (fBlockState [x][y])
  17055.                                       {
  17056.                                       DrawLine (hps, rcl.xLeft,  rcl.yBottom,
  17057.                                                      rcl.xRight, rcl.yTop) ;
  17058.  
  17059.                                       DrawLine (hps, rcl.xLeft,  rcl.yTop,
  17060.                                                      rcl.xRight, rcl.yBottom)
  17061.                                       }
  17062.                                  }
  17063.                   WinEndPaint (hps) ;
  17064.                   return 0 ;
  17065.              }
  17066.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  17067.         }
  17068.  
  17069.    The CHECKER1.DEF File
  17070.  
  17071.    ;-------------------------------------
  17072.    ; CHECKER1.DEF module definition file
  17073.    ;-------------------------------------
  17074.  
  17075.    NAME           CHECKER1  WINDOWAPI
  17076.  
  17077.    DESCRIPTION    'Mouse Hit-Test Program No. 1 (C) Charles Petzold, 1988'
  17078.    PROTMODE
  17079.    HEAPSIZE       1024
  17080.    STACKSIZE      8192
  17081.    EXPORTS        ClientWndProc
  17082.  
  17083.  
  17084.  The CHECKER1 display is shown in Figure 9-4.
  17085.  
  17086.  Each rectangle in CHECKER1 has the same width and height. The program
  17087.  determines the dimensions of the rectangles during the WM_SIZE message by
  17088.  dividing the client window width and height by 5. These dimensions are
  17089.  stored in xBlock and yBlock. The fBlockState array stores the state (checked
  17090.  or not checked) of each block. The state is TRUE if the rectangle contains
  17091.  an X and FALSE if it doesn't. The WM_PAINT message code tests the values in
  17092.  this array to determine if it should draw the Xs in the rectangles.
  17093.  
  17094.  The WM_BUTTON1DOWN code must work backward from the pointer coordinates to
  17095.  determine the particular block being clicked on. Because all the blocks are
  17096.  the same height and width, this task is fairly trivial, requiring only that
  17097.  the pointer coordinates be divided by the rectangle size:
  17098.  
  17099.    x = MOUSEMSG (&msg) -> x / xBlock ;
  17100.    y = MOUSEMSG (&msg) -> y / yBlock ;
  17101.  
  17102.  The values of x and y can range from 0 to 4, identifying the rectangle that
  17103.  the user clicked on. The value of fBlockState for that rectangle is
  17104.  inverted:
  17105.  
  17106.    fBlockState [x][y] = !fBlockState [x][y] ;
  17107.  
  17108.  The rectangle is then invalidated to generate a WM_PAINT message. If the
  17109.  width or height of the client window isn't equally divisible by 5, the
  17110.  program leaves a strip across the right or top of the window that isn't
  17111.  covered by any of the rectangles. If the user clicks on that area, the x or
  17112.  y value (calculated as shown above) will be greater than 4, in which case
  17113.  CHECKER1 beeps to indicate the error.
  17114.  
  17115.  Before we proceed to a more sophisticated hit-testing technique, let's add a
  17116.  keyboard interface to this program.
  17117.  
  17118.  Emulating the Mouse with the Keyboard
  17119.  I said at the outset of this chapter that you should write your Presentation
  17120.  Manager programs so they are usable with either a mouse or the keyboard. So
  17121.  far, I've been shamelessly ignoring that rule in order to concentrate on
  17122.  mouse logic. The CHECKER2 program, shown in Figure 9-5, adds a keyboard
  17123.  interface to CHECKER1. You can use the cursor movement keys to move the
  17124.  pointer from rectangle to rectangle. The Spacebar or Enter key draws an X or
  17125.  removes the X in the rectangle under the pointer.
  17126.  
  17127.  Figure 9-5.  The CHECKER2 program.
  17128.  
  17129.    The CHECKER2 File
  17130.  
  17131.    #--------------------
  17132.    # CHECKER2 make file
  17133.    #--------------------
  17134.  
  17135.    checker2.obj : checker2.c
  17136.         cl -c -G2sw -W3 checker2.c
  17137.  
  17138.    checker2.exe : checker2.obj checker2.def
  17139.         link checker2, /align:16, NUL, os2, checker2
  17140.  
  17141.    The CHECKER2.C File
  17142.  
  17143.    /*-------------------------------------------------------------------
  17144.       CHECKER2.C -- Mouse Hit-Test Demo Program with Keyboard Interface
  17145.      -------------------------------------------------------------------*/
  17146.  
  17147.    #define INCL_WIN
  17148.    #include <os2.h>
  17149.    #include <stdlib.h>
  17150.  
  17151.    #define DIVISIONS 5
  17152.  
  17153.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  17154.  
  17155.    int main (void)
  17156.         {
  17157.         static CHAR  szClientClass [] = "Checker2" ;
  17158.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  17159.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  17160.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  17161.         HAB          hab ;
  17162.         HMQ          hmq ;
  17163.         HWND         hwndFrame, hwndClient ;
  17164.         QMSG         qmsg ;
  17165.  
  17166.         hab = WinInitialize (0) ;
  17167.         hmq = WinCreateMsgQueue (hab, 0) ;
  17168.  
  17169.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  17170.  
  17171.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  17172.                                         &flFrameFlags, szClientClass, NULL,
  17173.                                         0L, NULL, 0, &hwndClient) ;
  17174.  
  17175.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  17176.              WinDispatchMsg (hab, &qmsg) ;
  17177.  
  17178.         WinDestroyWindow (hwndFrame) ;
  17179.         WinDestroyMsgQueue (hmq) ;
  17180.         WinTerminate (hab) ;
  17181.         return 0 ;
  17182.         }
  17183.  
  17184.    VOID DrawLine (HPS hps, LONG x1, LONG y1, LONG x2, LONG y2)
  17185.         {
  17186.         POINTL ptl ;
  17187.  
  17188.         ptl.x = x1 ;  ptl.y = y1 ;  GpiMove (hps, &ptl) ;
  17189.         ptl.x = x2 ;  ptl.y = y2 ;  GpiLine (hps, &ptl) ;
  17190.         }
  17191.  
  17192.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  17193.         {
  17194.         static BOOL  fBlockState [DIVISIONS] [DIVISIONS] ;
  17195.         static SHORT xBlock, yBlock ;
  17196.         HPS          hps ;
  17197.         POINTL       ptl ;
  17198.         RECTL        rcl ;
  17199.         SHORT        x, y ;
  17200.  
  17201.         switch (msg)
  17202.              {
  17203.              case WM_SIZE:
  17204.                   xBlock = SHORT1FROMMP (mp2) / DIVISIONS ;
  17205.                   yBlock = SHORT2FROMMP (mp2) / DIVISIONS ;
  17206.                   return 0 ;
  17207.  
  17208.              case WM_BUTTON1DOWN:
  17209.              case WM_BUTTON1DBLCLK:
  17210.                   if (xBlock > 0 && yBlock > 0)
  17211.                        {
  17212.                        x = MOUSEMSG(&msg)->x / xBlock ;
  17213.                        y = MOUSEMSG(&msg)->y / yBlock ;
  17214.  
  17215.                        if (x < DIVISIONS && y < DIVISIONS)
  17216.                             {
  17217.                             fBlockState [x][y] = !fBlockState [x][y] ;
  17218.  
  17219.                             rcl.xRight = xBlock + (rcl.xLeft   = x * xBlock) ;
  17220.                             rcl.yTop   = yBlock + (rcl.yBottom = y * yBlock) ;
  17221.  
  17222.                             WinInvalidateRect (hwnd, &rcl, FALSE) ;
  17223.                             }
  17224.                        else
  17225.                             WinAlarm (HWND_DESKTOP, WA_WARNING) ;
  17226.                        }
  17227.                   else
  17228.                        WinAlarm (HWND_DESKTOP, WA_WARNING) ;
  17229.  
  17230.                   break ;                       // do default processing
  17231.  
  17232.              case WM_SETFOCUS:
  17233.                   if (WinQuerySysValue (HWND_DESKTOP, SV_MOUSEPRESENT) == 0)
  17234.  
  17235.                        WinShowPointer (HWND_DESKTOP,
  17236.                                        SHORT1FROMMP (mp2) ? TRUE : FALSE) ;
  17237.                   return 0 ;
  17238.  
  17239.              case WM_CHAR:
  17240.                   if (xBlock == 0 || yBlock == 0)
  17241.                        break ;
  17242.  
  17243.                   if (CHARMSG(&msg)->fs & KC_KEYUP)
  17244.                        break ;
  17245.  
  17246.                   if (!(CHARMSG(&msg)->fs & KC_VIRTUALKEY))
  17247.                        break ;
  17248.  
  17249.                   WinQueryPointerPos (HWND_DESKTOP, &ptl) ;
  17250.                   WinMapWindowPoints (HWND_DESKTOP, hwnd, &ptl, 1) ;
  17251.  
  17252.                   x = max (0, min (DIVISIONS - 1, (SHORT) ptl.x / xBlock)) ;
  17253.                   y = max (0, min (DIVISIONS - 1, (SHORT) ptl.y / yBlock)) ;
  17254.  
  17255.                   switch (CHARMSG(&msg)->vkey)
  17256.                        {
  17257.                        case VK_LEFT:
  17258.                             x-- ;
  17259.                             break ;
  17260.  
  17261.                        case VK_RIGHT:
  17262.                             x++ ;
  17263.                             break ;
  17264.  
  17265.                        case VK_DOWN:
  17266.                             y-- ;
  17267.                             break ;
  17268.  
  17269.                        case VK_UP:
  17270.                             y++ ;
  17271.                             break ;
  17272.  
  17273.                        case VK_HOME:
  17274.                             x = 0 ;
  17275.                             y = DIVISIONS - 1 ;
  17276.                             break ;
  17277.  
  17278.                        case VK_END:
  17279.                             x = DIVISIONS - 1 ;
  17280.                             y = 0 ;
  17281.                             break ;
  17282.  
  17283.                        case VK_NEWLINE:
  17284.                        case VK_ENTER:
  17285.                        case VK_SPACE:
  17286.                             WinSendMsg (hwnd, WM_BUTTON1DOWN,
  17287.                                  MPFROM2SHORT (x * xBlock, y * yBlock), NULL)
  17288.                             break ;
  17289.  
  17290.                        default:
  17291.                             return 0 ;
  17292.                        }
  17293.                   x = (x + DIVISIONS) % DIVISIONS ;
  17294.                   y = (y + DIVISIONS) % DIVISIONS ;
  17295.  
  17296.                   ptl.x = x * xBlock + xBlock / 2 ;
  17297.                   ptl.y = y * yBlock + yBlock / 2 ;
  17298.  
  17299.                   WinMapWindowPoints (hwnd, HWND_DESKTOP, &ptl, 1) ;
  17300.                   WinSetPointerPos (HWND_DESKTOP, (SHORT) ptl.x, (SHORT) ptl.y
  17301.                   return 0 ;
  17302.  
  17303.              case WM_PAINT:
  17304.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  17305.                   GpiErase (hps) ;
  17306.  
  17307.                   if (xBlock > 0 && yBlock > 0)
  17308.                        for (x = 0 ; x < DIVISIONS ; x++)
  17309.                             for (y = 0 ; y < DIVISIONS ; y++)
  17310.                                  {
  17311.                                  rcl.xRight = xBlock + (rcl.xLeft   = x * xBlo
  17312.                                  rcl.yTop   = yBlock + (rcl.yBottom = y * yBlo
  17313.  
  17314.                                  WinDrawBorder (hps, &rcl, 1, 1,
  17315.                                                 CLR_NEUTRAL, CLR_BACKGROUND,
  17316.                                                 DB_STANDARD | DB_INTERIOR) ;
  17317.  
  17318.                                  if (fBlockState [x][y])
  17319.                                       {
  17320.                                       DrawLine (hps, rcl.xLeft,  rcl.yBottom,
  17321.                                                      rcl.xRight, rcl.yTop) ;
  17322.  
  17323.                                       DrawLine (hps, rcl.xLeft,  rcl.yTop,
  17324.                                                      rcl.xRight, rcl.yBottom)
  17325.                                       }
  17326.                                  }
  17327.                   WinEndPaint (hps) ;
  17328.                   return 0 ;
  17329.              }
  17330.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  17331.         }
  17332.  
  17333.    The CHECKER2.DEF File
  17334.  
  17335.    ;-------------------------------------
  17336.    ; CHECKER2.DEF module definition file
  17337.    ;-------------------------------------
  17338.  
  17339.    NAME           CHECKER2  WINDOWAPI
  17340.  
  17341.    DESCRIPTION    'Mouse Hit-Test Program No. 2 (C) Charles Petzold, 1988'
  17342.    PROTMODE
  17343.    HEAPSIZE       1024
  17344.    STACKSIZE      8192
  17345.    EXPORTS        ClientWndProc
  17346.  
  17347.  
  17348.  The first problem to be solved in a program like this involves the pointer
  17349.  itself. If no mouse is installed, how can the program use the pointer? Well,
  17350.  it's easier than you may think. Even if a mouse isn't installed, the pointer
  17351.  still exists and has a position on the display. You can determine that
  17352.  position from WinQueryPointerPos and set the position by calling
  17353.  WinSetPointerPos. If there's no mouse, calling WinSetPointerPos is the only
  17354.  way to move the pointer. Obviously, a user without a mouse would be annoyed
  17355.  to have a pointer sitting in the center of the display, so the Presentation
  17356.  Manager hides the pointer to make it invisible.
  17357.  
  17358.  The Presentation Manager maintains a value called the "pointer level," which
  17359.  determines whether the pointer is visible. Initially, the pointer level is
  17360.  set to 0 if a mouse is installed and to 1 if a mouse isn't installed. To
  17361.  decrement the pointer level, call
  17362.  
  17363.    WinShowPointer (HWND_DESKTOP, TRUE) ;
  17364.  
  17365.  The Presentation Manager will not decrement the pointer level below zero. To
  17366.  increment the pointer level, make the following call:
  17367.  
  17368.    WinShowPointer (HWND_DESKTOP, FALSE) ;
  17369.  
  17370.  The Presentation Manager displays the pointer only if the pointer level is
  17371.  equal to 0. (You can obtain the current pointer level from WinQuerySysValue
  17372.  with the SV_POINTERLEVEL parameter.)
  17373.  
  17374.  CHECKER2 shows how this works in practice. The program calls WinShowPointer
  17375.  in only one place──while processing the WM_SETFOCUS message:
  17376.  
  17377.    case WM_SETFOCUS:
  17378.         if (WinQuerySysValue (HWND_DESKTOP, SV_MOUSEPRESENT) == 0)
  17379.              WinShowPointer (HWND_DESKTOP,
  17380.                              SHORT1FROMMP (mp2) ? TRUE : FALSE) ;
  17381.         return 0 ;
  17382.  
  17383.  You'll recall from the discussion of WM_SETFOCUS in Chapter 8 that mp2 is
  17384.  TRUE if the window is gaining the input focus and FALSE if the window is
  17385.  losing the input focus.
  17386.  
  17387.  If a mouse isn't installed (indicated by a 0 value returned from
  17388.  WinQuerySysValue with the SV_MOUSEPRESENT parameter), the initial pointer
  17389.  level is 1, and the pointer is hidden. When CHECKER2 gets the input focus,
  17390.  it decrements the pointer level to 0. The pointer becomes visible. When
  17391.  CHECKER2 loses the input focus, the pointer level is incremented back to 1
  17392.  to hide the pointer again.
  17393.  
  17394.  This logic thus allows CHECKER2 to display the pointer whenever it has the
  17395.  input focus. Normally, input focus has nothing to do with the pointer. But
  17396.  CHECKER2 uses the keyboard to mimic the action of the mouse. It only makes
  17397.  sense to display the pointer when CHECKER2 has the input focus, because
  17398.  that's when CHECKER2 gets WM_CHAR messages.
  17399.  
  17400.  The button and repainting logic in ClientWndProc is the same as that in
  17401.  CHECKER1. The bulk of the new code is the addition of WM_CHAR processing.
  17402.  When CHECKER2 receives a WM_CHAR message, it obtains the position of the
  17403.  pointer in screen coordinates and converts the position to client window
  17404.  coordinates:
  17405.  
  17406.    WinQueryPointerPos (HWND_DESKTOP, &ptl) ;
  17407.    WinMapWindowPoints (HWND_DESKTOP, hwnd, &ptl, 1) ;
  17408.  
  17409.  The pointer could be outside the client window entirely. The program
  17410.  determines the values of x and y (ranging from 0 to 4) that identify the
  17411.  rectangle closest to the pointer:
  17412.  
  17413.    x = max (0, min (DIVISIONS - 1, (SHORT) ptl.x / xBlock)) ;
  17414.    y = max (0, min (DIVISIONS - 1, (SHORT) ptl.y / yBlock)) ;
  17415.  
  17416.  (The identifier DIVISIONS is defined as 5 near the top of the program.)
  17417.  These values of x and y are then incremented or decremented depending on the
  17418.  particular cursor movement key being pressed. The VK_NEWLINE, VK_ENTER, and
  17419.  VK_SPACE keys are processed by sending the window a WM_BUTTON1DOWN message
  17420.  to simulate a mouse button action.
  17421.  
  17422.  The new x and y values must then be converted back to a pointer position.
  17423.  The following formulas calculate a point in window coordinates at the center
  17424.  of the rectangle identified by x and y:
  17425.  
  17426.    ptl.x = x * xBlock + xBlock / 2 ;
  17427.    ptl.y = y * yBlock + yBlock / 2 ;
  17428.  
  17429.  CHECKER2 then converts that point to window coordinates and sets the new
  17430.  pointer position:
  17431.  
  17432.    WinMapWindowPoints (hwnd, HWND_DESKTOP, &ptl, 1) ;
  17433.    WinSetPointerPos (HWND_DESKTOP, (SHORT) ptl.x, (SHORT) ptl.y) ;
  17434.  
  17435.  Hit-Testing with Children
  17436.  
  17437.  Now let's try a different approach to hit-testing──one that involves
  17438.  creating child windows that process WM_BUTTON1DOWN messages themselves. The
  17439.  CHECKER3 program is shown in Figure 9-6.
  17440.  
  17441.  Figure 9-6.  The CHECKER3 program.
  17442.  
  17443.    The CHECKER3 File
  17444.  
  17445.    #--------------------
  17446.    # CHECKER3 make file
  17447.    #--------------------
  17448.  
  17449.    checker3.obj : checker3.c
  17450.         cl -c -G2sw -W3 checker3.c
  17451.  
  17452.    checker3.exe : checker3.obj checker3.def
  17453.         link checker3, /align:16, NUL, os2, checker3
  17454.  
  17455.    The CHECKER3.C File
  17456.  
  17457.    /*--------------------------------------------------------------
  17458.       CHECKER3.C -- Mouse Hit-Test Demo Program with Child Windows
  17459.      --------------------------------------------------------------*/
  17460.  
  17461.    #define INCL_WIN
  17462.    #include <os2.h>
  17463.  
  17464.    #define DIVISIONS 5
  17465.  
  17466.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  17467.    MRESULT EXPENTRY ChildWndProc  (HWND, USHORT, MPARAM, MPARAM) ;
  17468.  
  17469.    HAB  hab ;
  17470.  
  17471.    int main (void)
  17472.         {
  17473.         static CHAR  szClientClass [] = "Checker3" ;
  17474.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  17475.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  17476.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  17477.         HMQ          hmq ;
  17478.         HWND         hwndFrame, hwndClient ;
  17479.         QMSG         qmsg ;
  17480.  
  17481.         hab = WinInitialize (0) ;
  17482.         hmq = WinCreateMsgQueue (hab, 0) ;
  17483.  
  17484.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  17485.  
  17486.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  17487.                                         &flFrameFlags, szClientClass, NULL,
  17488.                                         0L, NULL, 0, &hwndClient) ;
  17489.  
  17490.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  17491.              WinDispatchMsg (hab, &qmsg) ;
  17492.  
  17493.         WinDestroyWindow (hwndFrame) ;
  17494.         WinDestroyMsgQueue (hmq) ;
  17495.         WinTerminate (hab) ;
  17496.         return 0 ;
  17497.         }
  17498.  
  17499.    VOID DrawLine (HPS hps, LONG x1, LONG y1, LONG x2, LONG y2)
  17500.         {
  17501.         POINTL ptl ;
  17502.  
  17503.         ptl.x = x1 ;  ptl.y = y1 ;  GpiMove (hps, &ptl) ;
  17504.         ptl.x = x2 ;  ptl.y = y2 ;  GpiLine (hps, &ptl) ;
  17505.         }
  17506.  
  17507.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  17508.         {
  17509.         static CHAR szChildClass [] = "Checker3.Child" ;
  17510.         static HWND hwndChild [DIVISIONS][DIVISIONS] ;
  17511.         SHORT       xBlock, yBlock, x, y ;
  17512.  
  17513.         switch (msg)
  17514.              {
  17515.              case WM_CREATE:
  17516.                   WinRegisterClass (hab, szChildClass, ChildWndProc,
  17517.                                     CS_SIZEREDRAW, sizeof (USHORT)) ;
  17518.  
  17519.                   for (x = 0 ; x < DIVISIONS ; x++)
  17520.                        for (y = 0 ; y < DIVISIONS ; y++)
  17521.  
  17522.                             hwndChild [x][y] =
  17523.                                  WinCreateWindow (
  17524.                                            hwnd,          // Parent window
  17525.                                            szChildClass,  // Window class
  17526.                                            NULL,          // Window text
  17527.                                            WS_VISIBLE,    // Window style
  17528.                                            0, 0, 0, 0,    // Position & size
  17529.                                            hwnd,          // Owner window
  17530.                                            HWND_BOTTOM,   // Placement
  17531.                                            y << 8 | x,    // Child window ID
  17532.                                            NULL,          // Control data
  17533.                                            NULL) ;        // Pres. Params
  17534.                   return 0 ;
  17535.  
  17536.              case WM_SIZE:
  17537.                   xBlock = SHORT1FROMMP (mp2) / DIVISIONS ;
  17538.                   yBlock = SHORT2FROMMP (mp2) / DIVISIONS ;
  17539.  
  17540.                   for (x = 0 ; x < DIVISIONS ; x++)
  17541.                        for (y = 0 ; y < DIVISIONS ; y++)
  17542.  
  17543.                             WinSetWindowPos (hwndChild [x][y], NULL,
  17544.                                  x * xBlock, y * yBlock, xBlock, yBlock,
  17545.                                  SWP_MOVE | SWP_SIZE) ;
  17546.                   return 0 ;
  17547.  
  17548.              case WM_BUTTON1DOWN:
  17549.              case WM_BUTTON1DBLCLK:
  17550.                   WinAlarm (HWND_DESKTOP, WA_WARNING) ;
  17551.                   break ;                       // do default processing
  17552.  
  17553.              case WM_ERASEBACKGROUND:
  17554.                   return 1 ;
  17555.              }
  17556.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  17557.         }
  17558.  
  17559.    MRESULT EXPENTRY ChildWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp
  17560.         {
  17561.         HPS   hps ;
  17562.         RECTL rcl ;
  17563.  
  17564.         switch (msg)
  17565.              {
  17566.              case WM_CREATE:
  17567.                   WinSetWindowUShort (hwnd, 0, 0) ;
  17568.                   return 0 ;
  17569.  
  17570.              case WM_BUTTON1DOWN:
  17571.              case WM_BUTTON1DBLCLK:
  17572.                   WinSetActiveWindow (HWND_DESKTOP, hwnd) ;
  17573.                   WinSetWindowUShort (hwnd, 0, !WinQueryWindowUShort (hwnd, 0)
  17574.                   WinInvalidateRect (hwnd, NULL, FALSE) ;
  17575.                   return 0 ;
  17576.  
  17577.              case WM_PAINT:
  17578.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  17579.  
  17580.                   WinQueryWindowRect (hwnd, &rcl) ;
  17581.  
  17582.                   WinDrawBorder (hps, &rcl, 1, 1, CLR_NEUTRAL, CLR_BACKGROUND,
  17583.                                       DB_STANDARD | DB_INTERIOR) ;
  17584.  
  17585.                   if (WinQueryWindowUShort (hwnd, 0))
  17586.                        {
  17587.                        DrawLine (hps, rcl.xLeft,  rcl.yBottom,
  17588.                                       rcl.xRight, rcl.yTop) ;
  17589.                        DrawLine (hps, rcl.xLeft,  rcl.yTop,
  17590.                                       rcl.xRight, rcl.yBottom) ;
  17591.                        }
  17592.                   WinEndPaint (hps) ;
  17593.                   return 0 ;
  17594.              }
  17595.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  17596.         }
  17597.  
  17598.    The CHECKER3.DEF File
  17599.  
  17600.    ;-------------------------------------
  17601.    ; CHECKER3.DEF module definition file
  17602.    ;-------------------------------------
  17603.  
  17604.    NAME           CHECKER3  WINDOWAPI
  17605.  
  17606.    DESCRIPTION    'Mouse Hit-Test Program No. 3 (C) Charles Petzold, 1988'
  17607.    PROTMODE
  17608.    HEAPSIZE       1024
  17609.    STACKSIZE      8192
  17610.    EXPORTS        ClientWndProc
  17611.                   ChildWndProc
  17612.  
  17613.  
  17614.  While processing the WM_CREATE message, ClientWndProc registers another
  17615.  window class named "Checker3.Child." Windows that are created based on the
  17616.  "Checker3.Child" class use the ChildWndProc window procedure for message
  17617.  processing. CHECKER3 then creates 25 child windows based on the
  17618.  "Checker3.Child" class:
  17619.  
  17620.    for (x = 0 ; x < DIVISIONS ; x++)
  17621.         for (y = 0 ; y < DIVISIONS ; y++)
  17622.  
  17623.              hwndChild [x] [y] =
  17624.                   WinCreateWindow (
  17625.                             hwnd,               // Parent window
  17626.                             szChildClass,       // Window class
  17627.                             NULL,               // Window text
  17628.                             WS_VISIBLE,         // Window style
  17629.                             0, 0, 0, 0,         // Position & size
  17630.                             hwnd,               // Owner window
  17631.                             HWND_BOTTOM,        // Placement
  17632.                             y << 8 | x,         // Child window ID
  17633.                             NULL,               // Control data
  17634.                             NULL) ;             // Pres. Params
  17635.  
  17636.  You've seen WinCreateWindow before. I used it in the WELCOME4 program in
  17637.  Chapter 3 to create three child windows based on predefined window classes.
  17638.  CHECKER3 uses WinCreateWindow to create windows based on the
  17639.  "Checker3.Child" class.
  17640.  
  17641.  In the WinCreateWindow function, the size and position parameters for these
  17642.  25 child windows are all set to 0. The windows must be sized and positioned
  17643.  based on the size of CHECKER3's client window. The sizing and positioning
  17644.  occur during the WM_SIZE message:
  17645.  
  17646.    case WM_SIZE:
  17647.         xBlock = SHORT1FROMMP (mp2) / DIVISIONS ;
  17648.         yBlock = SHORT2FROMMP (mp2) / DIVISIONS ;
  17649.  
  17650.         for (x = 0 ; x < DIVISIONS ; x++)
  17651.              for (y = 0 ; y < DIVISIONS ; y++)
  17652.  
  17653.                   WinSetWindowPos (hwndChild [x] [y], NULL,
  17654.                        x * xBlock, y * yBlock, xBlock, yBlock,
  17655.                        SWP_MOVE | SWP_SIZE) ;
  17656.         return 0 ;
  17657.  
  17658.  Each child window is set to one-fifth the height and one-fifth the width of
  17659.  CHECKER3's client window. Basically, instead of drawing 25 rectangles,
  17660.  CHECKER3 creates 25 child windows of the same size and position as the
  17661.  rectangles in CHECKER1 and CHECKER2. ClientWndProc doesn't do much else
  17662.  except call WinAlarm when it receives a WM_BUTTON1DOWN message.
  17663.  ClientWndProc receives this message only if the mouse is clicked in an area
  17664.  of the client window not covered by one of the children.
  17665.  
  17666.  Messages to the 25 child windows are processed in ChildWndProc. When
  17667.  CHECKER3 registers the "Checker3.Child" window class, it reserves 2 bytes of
  17668.  additional space (the size of a USHORT) for each window created based on
  17669.  that class:
  17670.  
  17671.    WinRegisterClass (hab, szChildClass, ChildWndProc,
  17672.                      CS_SIZEREDRAW, sizeof (USHORT)) ;
  17673.  
  17674.  ChildWndProc can access that USHORT by calling the WinSetWindowUShort and
  17675.  WinQueryWindowUShort functions. It uses the space to store the current state
  17676.  (X or no X) of the window. ChildWndProc initializes the reserved USHORT to 0
  17677.  (meaning no X) when it receives a WM_CREATE message:
  17678.  
  17679.    case WM_CREATE:
  17680.         WinSetWindowUShort (hwnd, 0, 0) ;
  17681.         return 0 ;
  17682.  
  17683.  ChildWndProc actually receives 25 WM_CREATE messages, 1 for each of the 25
  17684.  child windows. For each WM_CREATE message, the value of hwnd is different. A
  17685.  different reserved USHORT is initialized to 0 with each message.
  17686.  
  17687.  Each of the 25 child windows also receives a WM_PAINT message. Each window
  17688.  paints itself. The logic is somewhat simpler than in CHECKER1 and CHECKER2
  17689.  because the rectangle and the lines encompass the entire area of the child
  17690.  window. For example, to paint the rectangle around the window, the child
  17691.  need only obtain its window rectangle from WinQueryWindowRect and use that
  17692.  RECT structure directly in WinDrawBorder:
  17693.  
  17694.    WinQueryWindowRect (hwnd, &rcl) ;
  17695.  
  17696.    WinDrawBorder (hps, &rcl, 1, 1, CLR_NEUTRAL, CLR_BACKGROUND,
  17697.                   DB_STANDARD | DB_INTERIOR) ;
  17698.  
  17699.  The processing of the WM_BUTTON1DOWN message is also quite simple:
  17700.  
  17701.    case WM_BUTTON1DOWN:
  17702.    case WM_BUTTON1DBLCLK:
  17703.         WinSetActiveWindow (HWND_DESKTOP, hwnd) ;
  17704.         WinSetWindowUShort (hwnd, 0, !WinQueryWindowUShort (hwnd, 0)) ;
  17705.         WinInvalidateRect (hwnd, NULL, FALSE) ;
  17706.         return 0 ;
  17707.  
  17708.  A particular child window receives a WM_BUTTON1DOWN message when the child
  17709.  is underneath the pointer when the button was clicked. The code here obtains
  17710.  the value stored in the reserved USHORT using WinQueryWindowUShort, inverts
  17711.  it, and then stores it again using WinSetWindowUShort. The entire area of
  17712.  the child window is then invalidated to generate a WM_PAINT message for that
  17713.  child.
  17714.  
  17715.  CHECKER3.C is longer than CHECKER1.C. My explanation of CHECKER3 is longer
  17716.  than my explanation of CHECKER1. Despite that, I claim that CHECKER3 is
  17717.  simpler than CHECKER1. The reason? There's no real hit-testing in this
  17718.  program. If the child gets hit with a mouse click, it changes the state of
  17719.  itself without even examining the pointer position. If the rectangles in
  17720.  CHECKER1 were all different sizes, the hit-testing in that program would
  17721.  obviously be much more complex. But if the child windows in CHECKER3 were
  17722.  all different sizes, the logic in ChildWndProc wouldn't have to be changed
  17723.  at all.
  17724.  
  17725.  Just as you use subroutines to modularize your programs, you can use child
  17726.  windows to modularize the area of the client window and simplify mouse
  17727.  message processing.
  17728.  
  17729.  
  17730.  Tracking and Capturing
  17731.  
  17732.  So far we've seen a program that processes WM_MOUSEMOVE messages and a
  17733.  series of three programs that process WM_BUTTON1DOWN messages. However,
  17734.  often you'll have to use a combination of mouse movement and mouse button
  17735.  messages. You begin an action when a button is pressed, follow the movement
  17736.  of the mouse around the window, and then finish up when the button is
  17737.  released. This is sometimes called "tracking" the mouse, and some
  17738.  complexities are involved.
  17739.  
  17740.  Simple Mouse Tracking
  17741.  
  17742.  The BLOKOUT1 program in Figure 9-7 uses simple mouse tracking logic.
  17743.  
  17744.  Figure 9-7.  The BLOKOUT1 program.
  17745.  
  17746.    The BLOKOUT1 File
  17747.  
  17748.    #--------------------
  17749.    # BLOKOUT1 make file
  17750.    #--------------------
  17751.  
  17752.    blokout1.obj : blokout1.c
  17753.         cl -c -G2sw -W3 blokout1.c
  17754.  
  17755.    blokout1.exe : blokout1.obj blokout1.def
  17756.         link blokout1, /align:16, NUL, os2, blokout1
  17757.  
  17758.    The BLOKOUT1.C File
  17759.  
  17760.    /*-----------------------------------------
  17761.       BLOKOUT1.C -- Mouse Button Demo Program
  17762.      -----------------------------------------*/
  17763.  
  17764.    #define INCL_WIN
  17765.    #define INCL_GPI
  17766.    #include <os2.h>
  17767.  
  17768.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  17769.  
  17770.    int main (void)
  17771.  
  17772.         {
  17773.         static CHAR  szClientClass [] = "BlokOut1" ;
  17774.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  17775.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  17776.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  17777.         HAB          hab ;
  17778.         HMQ          hmq ;
  17779.         HWND         hwndFrame, hwndClient ;
  17780.         QMSG         qmsg ;
  17781.  
  17782.         hab = WinInitialize (0) ;
  17783.         hmq = WinCreateMsgQueue (hab, 0) ;
  17784.  
  17785.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  17786.  
  17787.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  17788.                                         &flFrameFlags, szClientClass, NULL,
  17789.                                         0L, NULL, 0, &hwndClient) ;
  17790.  
  17791.         WinSendMsg (hwndFrame, WM_SETICON,
  17792.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  17793.                     NULL) ;
  17794.  
  17795.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  17796.              WinDispatchMsg (hab, &qmsg) ;
  17797.  
  17798.         WinDestroyWindow (hwndFrame) ;
  17799.         WinDestroyMsgQueue (hmq) ;
  17800.         WinTerminate (hab) ;
  17801.         return 0 ;
  17802.         }
  17803.  
  17804.    VOID DrawBoxOutline (HWND hwnd, POINTL *pptlStart, POINTL *pptlEnd)
  17805.         {
  17806.         HPS hps ;
  17807.  
  17808.         hps = WinGetPS (hwnd) ;
  17809.         GpiSetMix (hps, FM_INVERT) ;
  17810.  
  17811.         GpiMove (hps, pptlStart) ;
  17812.         GpiBox (hps, DRO_OUTLINE, pptlEnd, 0L, 0L) ;
  17813.  
  17814.         WinReleasePS (hps) ;
  17815.         }
  17816.  
  17817.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  17818.         {
  17819.         static BOOL   fButtonDown, fValidBox ;
  17820.         static POINTL ptlStart, ptlEnd, ptlBoxStart, ptlBoxEnd ;
  17821.         HPS           hps ;
  17822.  
  17823.         switch (msg)
  17824.              {
  17825.              case WM_BUTTON1DOWN:
  17826.                   ptlStart.x = ptlEnd.x = MOUSEMSG(&msg)->x ;
  17827.                   ptlStart.y = ptlEnd.y = MOUSEMSG(&msg)->y ;
  17828.  
  17829.                   DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;
  17830.  
  17831.                   fButtonDown = TRUE ;
  17832.                   break ;                       // do default processing
  17833.  
  17834.              case WM_MOUSEMOVE:
  17835.                   if (fButtonDown)
  17836.                        {
  17837.                        DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;
  17838.  
  17839.                        ptlEnd.x = MOUSEMSG(&msg)->x ;
  17840.                        ptlEnd.y = MOUSEMSG(&msg)->y ;
  17841.  
  17842.                        DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;
  17843.                        }
  17844.                   break ;                       // do default processing
  17845.  
  17846.              case WM_BUTTON1UP:
  17847.                   if (fButtonDown)
  17848.                        {
  17849.                        DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;
  17850.  
  17851.                        ptlBoxStart = ptlStart ;
  17852.                        ptlBoxEnd.x = MOUSEMSG(&msg)->x ;
  17853.                        ptlBoxEnd.y = MOUSEMSG(&msg)->y ;
  17854.  
  17855.                        fButtonDown = FALSE ;
  17856.                        fValidBox = TRUE ;
  17857.                        WinInvalidateRect (hwnd, NULL, FALSE) ;
  17858.                        }
  17859.                   return 0 ;
  17860.  
  17861.              case WM_PAINT:
  17862.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  17863.                   GpiErase (hps) ;
  17864.  
  17865.                   if (fValidBox)
  17866.                        {
  17867.                        GpiMove (hps, &ptlBoxStart) ;
  17868.                        GpiBox (hps, DRO_OUTLINEFILL, &ptlBoxEnd, 0L, 0L) ;
  17869.                        }
  17870.                   if (fButtonDown)
  17871.                        {
  17872.                        GpiSetMix (hps, FM_INVERT) ;
  17873.  
  17874.                        GpiMove (hps, &ptlStart) ;
  17875.                        GpiBox (hps, DRO_OUTLINE, &ptlEnd, 0L, 0L) ;
  17876.                        }
  17877.                   WinEndPaint (hps) ;
  17878.                   return 0 ;
  17879.              }
  17880.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  17881.         }
  17882.  
  17883.    The BLOKOUT1.DEF File
  17884.  
  17885.    ;-------------------------------------
  17886.    ; BLOKOUT1.DEF module definition file
  17887.    ;-------------------------------------
  17888.  
  17889.    NAME           BLOKOUT1  WINDOWAPI
  17890.  
  17891.    DESCRIPTION    'Mouse Button Demo Program (C) Charles Petzold, 1988'
  17892.    PROTMODE
  17893.    HEAPSIZE       1024
  17894.    STACKSIZE      8192
  17895.    EXPORTS        ClientWndProc
  17896.  
  17897.  
  17898.  You can use this program to block out a rectangular area within the client
  17899.  window. When you press button 1, BLOKOUT1 saves the pointer position and
  17900.  uses it as one corner of a rectangle. You then move the mouse with the
  17901.  button pressed. The current position of the pointer is the opposite corner
  17902.  of the rectangle. As you move the mouse, BLOKOUT1 displays the rectangle
  17903.  outline. When you release the mouse button, the program draws the filled
  17904.  rectangle.
  17905.  
  17906.  Figure 9-8 shows one rectangle already drawn and another rectangle in
  17907.  progress.
  17908.  
  17909.  When ClientWndProc receives a WM_BUTTON1DOWN message, it saves the position
  17910.  of the pointer in two static POINTL structures:
  17911.  
  17912.    ptlStart.x = ptlEnd.x = MOUSEMSG(&msg) -> x ;
  17913.    ptlStart.y = ptlEnd.y = MOUSEMSG(&msg) -> y ;
  17914.  
  17915.  It then calls the function DrawBoxOutline to draw a rectangle using GpiBox
  17916.  with the FM_INVERT mix mode between these two points. (The rectangle will be
  17917.  only one pixel after this first call to DrawBoxOutline.) The fButtonDown
  17918.  variable is set to TRUE so that the program knows the button is down during
  17919.  subsequent messages.
  17920.  
  17921.  The WM_MOUSEMOVE message is processed only if fButtonDown is TRUE.
  17922.  DrawBoxOutline is called again to erase the previous box, the new pointer
  17923.  position is stored in ptlEnd, and the new rectangle outline is drawn.
  17924.  
  17925.  The WM_BUTTON1UP message is also processed only if fButtonDown is TRUE.
  17926.  ClientWndProc first erases the previous rectangle and then saves the two
  17927.  opposite corners in the POINTL structures ptlBoxStart and ptlBoxEnd. The
  17928.  fButtonDown variable is set to FALSE and the client window is invalidated.
  17929.  The WM_PAINT processing draws a filled rectangle based on these two points.
  17930.  
  17931.  At first, nothing seems to be wrong with this program. But a problem does
  17932.  exist.
  17933.  
  17934.  The Problem
  17935.  
  17936.  Try this: What happens if you press the mouse button within BLOKOUT1's
  17937.  client window but then move the pointer outside the window? BLOKOUT1 will
  17938.  stop receiving the WM_MOUSEMOVE messages. Now you release the mouse button.
  17939.  BLOKOUT1 doesn't get that WM_BUTTON1UP message because the pointer is
  17940.  outside the client window.
  17941.  
  17942.  Now move the mouse pointer back within BLOKOUT1's client window.
  17943.  ClientWndProc still thinks the mouse button is pressed because fButtonDown
  17944.  is set to TRUE! This is clearly not good. The program doesn't know what's
  17945.  going on.
  17946.  
  17947.  An alternative is to dispense with the fButtonDown variable and use
  17948.  WinGetKeyState to test the state of the button during the WM_MOUSEMOVE
  17949.  message. But this is also a problem. What happens if you press the mouse
  17950.  button outside of BLOKOUT1's client window and then move the pointer inside?
  17951.  WinGetKeyState will report that the mouse button is pressed, but BLOKOUT1
  17952.  will not have a valid starting point for the rectangle because the button
  17953.  was pressed outside the client window.
  17954.  
  17955.  How about using a combination of the fButtonDown logic and the
  17956.  WinGetKeyState function? You're welcome to try, but think a bit about what
  17957.  you really want to do here. You want the ability to follow the mouse pointer
  17958.  even when it ventures outside the client window. You want to process all of
  17959.  the WM_MOUSEMOVE messages between WM_BUTTON1DOWN and WM_BUTTON1UP,
  17960.  regardless of whether the mouse is inside or outside the client window.
  17961.  
  17962.  You can do this. It's called "capturing the mouse."
  17963.  
  17964.  The Solution──Capturing the Mouse
  17965.  
  17966.  Capturing the mouse is simpler than baiting a mousetrap. You simply call
  17967.  
  17968.    WinSetCapture (HWND_DESKTOP, hwnd) ;
  17969.  
  17970.  After you call WinSetCapture, all mouse messages will be directed to hwnd's
  17971.  window procedure regardless of where the pointer is positioned. (Note that
  17972.  the coordinates of the pointer will still be relative to the lower-left
  17973.  corner of the window, so they could be negative.) To release the mouse, use
  17974.  the following call:
  17975.  
  17976.    WinSetCapture (HWND_DESKTOP, NULL) ;
  17977.  
  17978.  A window that has captured the mouse is called the "capture window." Only
  17979.  one window can be the capture window at any time. You can obtain the window
  17980.  handle of the capture window by calling WinQueryCapture. The function
  17981.  returns NULL if there is no capture window, as is usually the case.
  17982.  
  17983.  The BLOKOUT2 program, shown in Figure 9-9, demonstrates how to capture the
  17984.  mouse.
  17985.  
  17986.  Figure 9-9.  The BLOKOUT2 program.
  17987.  
  17988.    The BLOKOUT2 File
  17989.  
  17990.    #--------------------
  17991.    # BLOKOUT2 make file
  17992.    #--------------------
  17993.  
  17994.    blokout2.obj : blokout2.c
  17995.         cl -c -G2sw -W3 blokout2.c
  17996.  
  17997.    blokout2.exe : blokout2.obj blokout2.def
  17998.         link blokout2, /align:16, NUL, os2, blokout2
  17999.  
  18000.    The BLOKOUT2.C File
  18001.  
  18002.    /*---------------------------------------------------
  18003.       BLOKOUT2.C -- Mouse Button & Capture Demo Program
  18004.      ---------------------------------------------------*/
  18005.  
  18006.    #define INCL_WIN
  18007.    #define INCL_GPI
  18008.    #include <os2.h>
  18009.  
  18010.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  18011.  
  18012.    int main (void)
  18013.         {
  18014.         static CHAR  szClientClass [] = "BlokOut2" ;
  18015.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  18016.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  18017.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  18018.         HAB          hab ;
  18019.         HMQ          hmq ;
  18020.  
  18021.         HWND         hwndFrame, hwndClient ;
  18022.         QMSG         qmsg ;
  18023.  
  18024.         hab = WinInitialize (0) ;
  18025.         hmq = WinCreateMsgQueue (hab, 0) ;
  18026.  
  18027.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  18028.  
  18029.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  18030.                                         &flFrameFlags, szClientClass, NULL,
  18031.                                         0L, NULL, 0, &hwndClient) ;
  18032.  
  18033.         WinSendMsg (hwndFrame, WM_SETICON,
  18034.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  18035.                     NULL) ;
  18036.  
  18037.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  18038.              WinDispatchMsg (hab, &qmsg) ;
  18039.  
  18040.         WinDestroyWindow (hwndFrame) ;
  18041.         WinDestroyMsgQueue (hmq) ;
  18042.         WinTerminate (hab) ;
  18043.         return 0 ;
  18044.         }
  18045.  
  18046.    VOID DrawBoxOutline (HWND hwnd, POINTL *pptlStart, POINTL *pptlEnd)
  18047.         {
  18048.         HPS hps ;
  18049.  
  18050.         hps = WinGetPS (hwnd) ;
  18051.         GpiSetMix (hps, FM_INVERT) ;
  18052.  
  18053.         GpiMove (hps, pptlStart) ;
  18054.         GpiBox (hps, DRO_OUTLINE, pptlEnd, 0L, 0L) ;
  18055.  
  18056.         WinReleasePS (hps) ;
  18057.         }
  18058.  
  18059.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  18060.         {
  18061.         static BOOL   fCapture, fValidBox ;
  18062.         static POINTL ptlStart, ptlEnd, ptlBoxStart, ptlBoxEnd ;
  18063.         HPS           hps ;
  18064.  
  18065.         switch (msg)
  18066.              {
  18067.  
  18068.              case WM_BUTTON1DOWN:
  18069.                   ptlStart.x = ptlEnd.x = MOUSEMSG(&msg)->x ;
  18070.                   ptlStart.y = ptlEnd.y = MOUSEMSG(&msg)->y ;
  18071.  
  18072.                   DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;
  18073.  
  18074.                   WinSetCapture (HWND_DESKTOP, hwnd) ;
  18075.                   fCapture = TRUE ;
  18076.                   break ;                       // do default processing
  18077.  
  18078.              case WM_MOUSEMOVE:
  18079.                   if (fCapture)
  18080.                        {
  18081.                        DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;
  18082.  
  18083.                        ptlEnd.x = MOUSEMSG(&msg)->x ;
  18084.                        ptlEnd.y = MOUSEMSG(&msg)->y ;
  18085.  
  18086.                        DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;
  18087.                        }
  18088.                   break ;                       // do default processing
  18089.  
  18090.              case WM_BUTTON1UP:
  18091.                   if (fCapture)
  18092.                        {
  18093.                        DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;
  18094.  
  18095.                        ptlBoxStart = ptlStart ;
  18096.                        ptlBoxEnd.x = MOUSEMSG(&msg)->x ;
  18097.                        ptlBoxEnd.y = MOUSEMSG(&msg)->y ;
  18098.  
  18099.                        WinSetCapture (HWND_DESKTOP, NULL) ;
  18100.                        fCapture = FALSE ;
  18101.                        fValidBox = TRUE ;
  18102.                        WinInvalidateRect (hwnd, NULL, FALSE) ;
  18103.                        }
  18104.                   return 0 ;
  18105.  
  18106.              case WM_CHAR:
  18107.                   if (fCapture && CHARMSG(&msg)->fs   &  KC_VIRTUALKEY &&
  18108.                                 !(CHARMSG(&msg)->fs   &  KC_KEYUP)     &&
  18109.                                 CHARMSG(&msg)->vkey == VK_ESC)
  18110.                        {
  18111.                        DrawBoxOutline (hwnd, &ptlStart, &ptlEnd) ;
  18112.  
  18113.                        WinSetCapture (HWND_DESKTOP, NULL) ;
  18114.  
  18115.                        fCapture = FALSE ;
  18116.                        }
  18117.                   return 0 ;
  18118.  
  18119.              case WM_PAINT:
  18120.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  18121.                   GpiErase (hps) ;
  18122.  
  18123.                   if (fValidBox)
  18124.                        {
  18125.                        GpiMove (hps, &ptlBoxStart) ;
  18126.                        GpiBox (hps, DRO_OUTLINEFILL, &ptlBoxEnd, 0L, 0L) ;
  18127.                        }
  18128.                   if (fCapture)
  18129.                        {
  18130.                        GpiSetMix (hps, FM_INVERT) ;
  18131.                        GpiMove (hps, &ptlStart) ;
  18132.                        GpiBox (hps, DRO_OUTLINE, &ptlEnd, 0L, 0L) ;
  18133.                        }
  18134.                   WinEndPaint (hps) ;
  18135.                   return 0 ;
  18136.              }
  18137.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  18138.         }
  18139.  
  18140.    The BLOKOUT2.DEF File
  18141.  
  18142.    ;-------------------------------------
  18143.    ; BLOKOUT2.DEF module definition file
  18144.    ;-------------------------------------
  18145.  
  18146.    NAME           BLOKOUT2  WINDOWAPI
  18147.  
  18148.    DESCRIPTION    'Mouse Button & Capture Demo Program (C) Charles Petzold, 19
  18149.    PROTMODE
  18150.    HEAPSIZE       1024
  18151.    STACKSIZE      8192
  18152.    EXPORTS        ClientWndProc
  18153.  
  18154.  
  18155.  BLOKOUT2 captures the mouse on a WM_BUTTON1DOWN message. Rather than using
  18156.  the fButtonDown variable from BLOKOUT1, BLOKOUT2 uses an fCapture variable
  18157.  that it sets to TRUE when the mouse is captured.
  18158.  
  18159.  If the size of the BLOKOUT2 window is less than the full screen, you'll
  18160.  notice that BLOKOUT2 continues to receive WM_MOUSEMOVE messages even when
  18161.  you move the pointer outside the client window. When you release the button
  18162.  (either while the pointer is inside or outside the client window), BLOKOUT2
  18163.  releases the mouse.
  18164.  
  18165.  BLOKOUT2 also processes the WM_CHAR message. If you press the Escape key
  18166.  while blocking out a rectangle, the program erases the rectangle you've been
  18167.  drawing and releases the mouse. This is how you can cancel the block-out.
  18168.  Otherwise, much of BLOKOUT2 is exactly the same as BLOKOUT1. Capturing the
  18169.  mouse adds very little overhead to mouse processing and helps out a lot in
  18170.  many cases.
  18171.  
  18172.  The Presentation Manager WinTrackRect function is also a big help in jobs
  18173.  that require a rectangle to be stretched or moved using the mouse and
  18174.  keyboard, particularly when the rectangle must be displayed outside the
  18175.  program's window. The title bar window and sizing border window use
  18176.  WinTrackRect to let you move and resize the standard window. I'll show you
  18177.  how to use the WinTrackRect in the BLOWUP program in Chapter 15.
  18178.  
  18179.  The SKETCH Program
  18180.  
  18181.  You've heard of CAD programs? You've heard of paint programs? The program in
  18182.  Figure 9-10 is neither of these. It's called SKETCH and is just about the
  18183.  most primitive drawing program possible.
  18184.  
  18185.  Figure 9-10.  The SKETCH program.
  18186.  
  18187.    The SKETCH File
  18188.  
  18189.    #------------------
  18190.    # SKETCH make file
  18191.    #------------------
  18192.  
  18193.    sketch.obj : sketch.c
  18194.         cl -c -G2sw -W3 sketch.c
  18195.  
  18196.    sketch.exe : sketch.obj sketch.def
  18197.         link sketch, /align:16, NUL, os2, sketch
  18198.  
  18199.    The SKETCH.C File
  18200.  
  18201.    /*-------------------------------------
  18202.       SKETCH.C -- Mouse Sketching Program
  18203.      -------------------------------------*/
  18204.  
  18205.    #define INCL_WIN
  18206.  
  18207.    #define INCL_GPI
  18208.    #include <os2.h>
  18209.  
  18210.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  18211.  
  18212.    HAB  hab ;
  18213.  
  18214.    int main (void)
  18215.         {
  18216.         static CHAR  szClientClass [] = "Sketch" ;
  18217.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  18218.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  18219.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  18220.         HMQ          hmq ;
  18221.         HWND         hwndFrame, hwndClient ;
  18222.         QMSG         qmsg ;
  18223.  
  18224.         hab = WinInitialize (0) ;
  18225.         hmq = WinCreateMsgQueue (hab, 0) ;
  18226.  
  18227.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  18228.  
  18229.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  18230.                                         &flFrameFlags, szClientClass, NULL,
  18231.                                         0L, NULL, 0, &hwndClient) ;
  18232.  
  18233.         if (hwndFrame == NULL)
  18234.              WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
  18235.                             "Not enough memory to create the "
  18236.                             "bitmap used for storing images.",
  18237.                             szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;
  18238.         else
  18239.              {
  18240.              WinSendMsg (hwndFrame, WM_SETICON,
  18241.                          WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE
  18242.                          NULL) ;
  18243.  
  18244.              while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  18245.                   WinDispatchMsg (hab, &qmsg) ;
  18246.  
  18247.              WinDestroyWindow (hwndFrame) ;
  18248.              }
  18249.  
  18250.         WinDestroyMsgQueue (hmq) ;
  18251.         WinTerminate (hab) ;
  18252.         return 0 ;
  18253.         }
  18254.  
  18255.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  18256.         {
  18257.         static BOOL      fButton1Down, fButton2Down ;
  18258.         static HBITMAP   hbm ;
  18259.         static HDC       hdcMemory ;
  18260.         static HPS       hpsMemory ;
  18261.         static POINTL    ptlPointerPos, aptl [3] ;
  18262.         BITMAPINFOHEADER bmp ;
  18263.         HPS              hpsWindow ;
  18264.         LONG             cxFullScrn, cyFullScrn ;
  18265.         SIZEL            sizl ;
  18266.  
  18267.         switch (msg)
  18268.              {
  18269.              case WM_CREATE:
  18270.                   cxFullScrn = WinQuerySysValue (HWND_DESKTOP, SV_CXFULLSCREEN
  18271.                   cyFullScrn = WinQuerySysValue (HWND_DESKTOP, SV_CYFULLSCREEN
  18272.  
  18273.                             /*-------------------------
  18274.                                Create Memory DC and PS
  18275.                               -------------------------*/
  18276.  
  18277.                   hdcMemory = DevOpenDC (hab, OD_MEMORY, "*", 0L, NULL, NULL)
  18278.  
  18279.                   sizl.cx = 0 ;
  18280.                   sizl.cy = 0 ;
  18281.                   hpsMemory = GpiCreatePS (hab, hdcMemory, &sizl,
  18282.                                            PU_PELS    | GPIF_DEFAULT |
  18283.                                            GPIT_MICRO | GPIA_ASSOC) ;
  18284.  
  18285.                             /*----------------------------------------------
  18286.                                Create monochrome bitmap, return 1 if cannot
  18287.                               ----------------------------------------------*/
  18288.  
  18289.                   bmp.cbFix     = sizeof bmp ;
  18290.                   bmp.cx        = (SHORT) cxFullScrn ;
  18291.                   bmp.cy        = (SHORT) cyFullScrn ;
  18292.                   bmp.cPlanes   = 1 ;
  18293.                   bmp.cBitCount = 1 ;
  18294.                   hbm = GpiCreateBitmap (hpsMemory, &bmp, 0L, 0L, NULL) ;
  18295.  
  18296.                   if (hbm == NULL)
  18297.                        {
  18298.                        GpiDestroyPS (hpsMemory) ;
  18299.                        DevCloseDC (hdcMemory) ;
  18300.                        return 1 ;
  18301.                        }
  18302.  
  18303.                             /*--------------------------------------
  18304.                                Set bitmap in memory PS and clear it
  18305.                               --------------------------------------*/
  18306.  
  18307.                   GpiSetBitmap (hpsMemory, hbm) ;
  18308.  
  18309.                   aptl[1].x = cxFullScrn ;
  18310.                   aptl[1].y = cyFullScrn ;
  18311.                   GpiBitBlt (hpsMemory, NULL, 2L, aptl, ROP_ZERO, BBO_OR) ;
  18312.                   return 0 ;
  18313.  
  18314.              case WM_BUTTON1DOWN:
  18315.                   if (!fButton2Down)
  18316.                        WinSetCapture (HWND_DESKTOP, hwnd) ;
  18317.  
  18318.                   ptlPointerPos.x = MOUSEMSG(&msg)->x ;
  18319.                   ptlPointerPos.y = MOUSEMSG(&msg)->y ;
  18320.  
  18321.                   fButton1Down = TRUE ;
  18322.                   break ;                       // do default processing
  18323.  
  18324.              case WM_BUTTON1UP:
  18325.                   if (!fButton2Down)
  18326.                        WinSetCapture (HWND_DESKTOP, NULL) ;
  18327.  
  18328.                   fButton1Down = FALSE ;
  18329.                   return 0 ;
  18330.  
  18331.              case WM_BUTTON2DOWN:
  18332.                   if (!fButton1Down)
  18333.                        WinSetCapture (HWND_DESKTOP, hwnd) ;
  18334.  
  18335.                   ptlPointerPos.x = MOUSEMSG(&msg)->x ;
  18336.                   ptlPointerPos.y = MOUSEMSG(&msg)->y ;
  18337.  
  18338.                   fButton2Down = TRUE ;
  18339.                   break ;                       // do default processing
  18340.  
  18341.              case WM_BUTTON2UP:
  18342.                   if (!fButton1Down)
  18343.                        WinSetCapture (HWND_DESKTOP, NULL) ;
  18344.  
  18345.                   fButton2Down = FALSE ;
  18346.                   return 0 ;
  18347.  
  18348.              case WM_MOUSEMOVE:
  18349.                   if (!fButton1Down && !fButton2Down)
  18350.                        break ;
  18351.  
  18352.                   hpsWindow = WinGetPS (hwnd) ;
  18353.  
  18354.                   GpiSetColor (hpsMemory, fButton1Down ? CLR_TRUE : CLR_FALSE)
  18355.                   GpiSetColor (hpsWindow,
  18356.                                fButton1Down ? CLR_NEUTRAL : CLR_BACKGROUND) ;
  18357.  
  18358.                   GpiMove (hpsMemory, &ptlPointerPos) ;
  18359.                   GpiMove (hpsWindow, &ptlPointerPos) ;
  18360.  
  18361.                   ptlPointerPos.x = MOUSEMSG(&msg)->x ;
  18362.                   ptlPointerPos.y = MOUSEMSG(&msg)->y ;
  18363.  
  18364.                   GpiLine (hpsMemory, &ptlPointerPos) ;
  18365.                   GpiLine (hpsWindow, &ptlPointerPos) ;
  18366.  
  18367.                   WinReleasePS (hpsWindow) ;
  18368.                   break ;                       // do default processing
  18369.  
  18370.              case WM_PAINT:
  18371.                   hpsWindow = WinBeginPaint (hwnd, NULL, (PRECTL) aptl) ;
  18372.  
  18373.                   aptl[2] = aptl[0] ;
  18374.  
  18375.                   GpiBitBlt (hpsWindow, hpsMemory, 3L, aptl, ROP_SRCCOPY,
  18376.                              BBO_OR) ;
  18377.  
  18378.                   WinEndPaint (hpsWindow) ;
  18379.                   return 0 ;
  18380.  
  18381.              case WM_DESTROY:
  18382.                   GpiDestroyPS (hpsMemory) ;
  18383.                   DevCloseDC (hdcMemory) ;
  18384.                   GpiDeleteBitmap (hbm) ;
  18385.                   return 0 ;
  18386.              }
  18387.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  18388.         }
  18389.  
  18390.    The SKETCH.DEF File
  18391.  
  18392.    ;-----------------------------------
  18393.    ; SKETCH.DEF module definition file
  18394.    ;-----------------------------------
  18395.  
  18396.    NAME           SKETCH    WINDOWAPI
  18397.  
  18398.    DESCRIPTION    'Mouse Sketching Program (C) Charles Petzold, 1988'
  18399.    PROTMODE
  18400.    HEAPSIZE       1024
  18401.    STACKSIZE      8192
  18402.    EXPORTS        ClientWndProc
  18403.  
  18404.  
  18405.  To draw in SKETCH, you press button 1 and move the mouse. To erase (or, more
  18406.  precisely, to draw in the background color) you press button 2 and move the
  18407.  mouse. To clear the entire window, you.... Well, you have to end the
  18408.  program, load it again, and start all over. (I said that this is a primitive
  18409.  program.) Figure 9-11 shows the SKETCH program with the word "Hello" drawn
  18410.  on the window, an homage to those early advertisements for the Apple
  18411.  Macintosh.
  18412.  
  18413.  During the WM_CREATE message, SKETCH creates a monochrome bitmap the size of
  18414.  the maximized window. If this is not possible, SKETCH returns 1 from the
  18415.  WM_CREATE message. Returning 1 from WM_CREATE causes the creation of the
  18416.  standard window to be aborted. In main, SKETCH checks the value of hwndFrame
  18417.  to see if WinCreateStdWindow was successful. If not, SKETCH displays a
  18418.  message box informing the user of the problem.
  18419.  
  18420.  This bitmap is used in a memory device context for saving any drawing you do
  18421.  and updating the window during the WM_PAINT message. A bitmap used in this
  18422.  way is sometimes called a "shadow bitmap." Whenever SKETCH draws something
  18423.  on the window, it also draws the same thing on the bitmap. Consequently,
  18424.  WM_PAINT processing is very simple. All that's required is a GpiBitBlt call
  18425.  to update the window from the bitmap.
  18426.  
  18427.  
  18428.  Chapter 10  Setting the Timer
  18429.  ───────────────────────────────────────────────────────────────────────────
  18430.  
  18431.  
  18432.  The Presentation Manager timer is a form of input that periodically notifies
  18433.  a window procedure when a specified amount of time has elapsed. Your program
  18434.  specifies this time in the WinStartTimer function. The Presentation Manager
  18435.  then periodically posts WM_TIMER messages to the program's window procedure.
  18436.  
  18437.  A clock is the most obvious application for a timer. The WM_TIMER messages
  18438.  signal the program to update the clock display. (Later in this chapter we'll
  18439.  write two clock programs that use the timer.) You can also use the timer to
  18440.  periodically update a status report (as is done in the FREEMEM program also
  18441.  shown in this chapter) or to pace screen activity for animation or
  18442.  computer-aided instruction.
  18443.  
  18444.  
  18445.  Why the Timer Is Necessary
  18446.  
  18447.  In previous chapters you've seen how the Presentation Manager provides
  18448.  alternatives to several categories of OS/2 kernel functions. For example, a
  18449.  Presentation Manager program doesn't use the OS/2 kernel VIO functions to
  18450.  write to the display (unless, of course, the application uses the Advanced
  18451.  VIO facility). Instead, the program writes to the screen using the
  18452.  Presentation Manager GPI functions. Similarly, a Presentation Manager
  18453.  program doesn't use the OS/2 kernel KBD or MOU functions for keyboard or
  18454.  mouse input. Instead, the program processes keyboard and mouse input in the
  18455.  form of messages.
  18456.  
  18457.  program, DosSleep would suspend the normal processing of messages in that
  18458.  thread──even messages for such basic tasks as moving and resizing the
  18459.  window or selecting an item from the program's system menu. It's clear that
  18460.  for the Presentation Manager, which requires threads to process messages as
  18461.  quickly as possible (window procedures should take no longer than one-tenth
  18462.  of a second to process a message), you shouldn't call functions such as
  18463.  DosSleep or DosSemSetWait in a message queue thread if you want optimum
  18464.  performance. That's why the Presentation Manager includes the timer. Message
  18465.  queue threads use the timer to regain periodic control in the absence of
  18466.  user input and other messages to the thread's windows. Chapter 17 discusses
  18467.  some alternatives to this use of the timer, including the use of multiple
  18468.  threads of execution.
  18469.  
  18470.  
  18471.  Timer Basics
  18472.  
  18473.  The timer is a fairly simple facility involving two functions and one
  18474.  message. The Presentation Manager defines two ways to set a timer. Both use
  18475.  the same WinStartTimer function but in a somewhat different format. The
  18476.  first method is by far the most common.
  18477.  
  18478.  The Common Method of Using a Timer
  18479.  
  18480.  The BEEPER1 program, shown in Figure 10-1, shows how to start a timer,
  18481.  process WM_TIMER messages, and stop the timer. BEEPER1 sets the timer to go
  18482.  off once every second. The window procedure responds to a WM_TIMER message
  18483.  by beeping and changing the color of its client window, alternating between
  18484.  red and blue.
  18485.  
  18486.  Figure 10-1.  The BEEPER1 program.
  18487.  
  18488.    The BEEPER1 File
  18489.  
  18490.    #-------------------
  18491.    # BEEPER1 make file
  18492.    #-------------------
  18493.  
  18494.    beeper1.obj : beeper1.c
  18495.         cl -c -G2sw -W3 beeper1.c
  18496.  
  18497.    beeper1.exe : beeper1.obj beeper1.def
  18498.         link beeper1, /align:16, NUL, os2, beeper1
  18499.  
  18500.    The BEEPER1.C File
  18501.  
  18502.    /*---------------------------------------
  18503.       BEEPER1.C -- Timer Demo Program No. 1
  18504.      ---------------------------------------*/
  18505.  
  18506.    #define INCL_WIN
  18507.    #include <os2.h>
  18508.  
  18509.    #define ID_TIMER 1
  18510.  
  18511.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  18512.  
  18513.    int main (void)
  18514.         {
  18515.         static char  szClientClass [] = "Beeper1" ;
  18516.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  18517.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  18518.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  18519.         HAB          hab ;
  18520.         HMQ          hmq ;
  18521.         HWND         hwndFrame, hwndClient ;
  18522.         QMSG         qmsg ;
  18523.  
  18524.         hab = WinInitialize (0) ;
  18525.         hmq = WinCreateMsgQueue (hab, 0) ;
  18526.  
  18527.         WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;
  18528.  
  18529.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  18530.                                         &flFrameFlags, szClientClass, NULL,
  18531.                                         0L, NULL, 0, &hwndClient) ;
  18532.  
  18533.         WinStartTimer (hab, hwndClient, ID_TIMER, 1000) ;
  18534.  
  18535.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  18536.              WinDispatchMsg (hab, &qmsg) ;
  18537.  
  18538.         WinStopTimer (hab, hwndClient, ID_TIMER) ;
  18539.  
  18540.         WinDestroyWindow (hwndFrame) ;
  18541.         WinDestroyMsgQueue (hmq) ;
  18542.         WinTerminate (hab) ;
  18543.         return 0 ;
  18544.         }
  18545.  
  18546.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  18547.         {
  18548.         static BOOL fFlipFlop ;
  18549.         HPS         hps ;
  18550.         RECTL       rcl ;
  18551.  
  18552.         switch (msg)
  18553.              {
  18554.              case WM_TIMER:
  18555.                   WinAlarm (HWND_DESKTOP, WA_NOTE) ;
  18556.                   fFlipFlop = !fFlipFlop ;
  18557.                   WinInvalidateRect (hwnd, NULL, FALSE) ;
  18558.                   return 0 ;
  18559.  
  18560.              case WM_PAINT:
  18561.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  18562.  
  18563.                   WinQueryWindowRect (hwnd, &rcl) ;
  18564.                   WinFillRect (hps, &rcl, fFlipFlop ? CLR_BLUE : CLR_RED) ;
  18565.  
  18566.                   WinEndPaint (hps) ;
  18567.                   return 0 ;
  18568.              }
  18569.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  18570.         }
  18571.  
  18572.    The BEEPER1.DEF File
  18573.  
  18574.    ;------------------------------------
  18575.    ; BEEPER1.DEF module definition file
  18576.    ;------------------------------------
  18577.  
  18578.    NAME           BEEPER1   WINDOWAPI
  18579.  
  18580.    DESCRIPTION    'Timer Demo Program No. 1 (C) Charles Petzold, 1988'
  18581.    PROTMODE
  18582.    HEAPSIZE       1024
  18583.    STACKSIZE      8192
  18584.    EXPORTS        ClientWndProc
  18585.  
  18586.  
  18587.  The general syntax of the WinStartTimer function is
  18588.  
  18589.    WinStartTimer (hab, hwnd, idTimer, usMsecInterval) ;
  18590.  
  18591.  The hwnd parameter is the window handle that designates which window
  18592.  function receives the WM_TIMER messages. The idTimer parameter is a number
  18593.  you select to identify this particular timer. The usMsecInterval parameter
  18594.  specifies a time interval in milliseconds (msec). This is the rate at which
  18595.  the Presentation Manager posts the WM_TIMER messages in the message queue.
  18596.  The value can range from 0 (which delivers WM_TIMER messages as fast as the
  18597.  computer's hardware clock) to 65,535 msec, or a little more than a minute.
  18598.  
  18599.  BEEPER1 starts a timer in main immediately following the WinCreateStdWindow
  18600.  call:
  18601.  
  18602.    WinStartTimer (hab, hwndClient, ID_TIMER, 1000) ;
  18603.  
  18604.  This tells the Presentation Manager to post a WM_TIMER message to hwndClient
  18605.  once every 1000 msec. The ID_TIMER identifier is defined near the top of the
  18606.  program:
  18607.  
  18608.    #define ID_TIMER 1
  18609.  
  18610.  The low USHORT of the mp1 parameter that accompanies the WM_TIMER message is
  18611.  this ID number.
  18612.  
  18613.  BEEPER1 processes the WM_TIMER messages in ClientWndProc:
  18614.  
  18615.    case WM_TIMER:
  18616.         WinAlarm (HWND_DESKTOP, WA_NOTE) ;
  18617.         fFlipFlop = !fFlipFlop ;
  18618.         WinInvalidateRect (hwnd, NULL, FALSE) ;
  18619.         return 0 ;
  18620.  
  18621.  The code simply beeps the speaker, inverts the value of the static BOOL
  18622.  variable fFlipFlop, and calls WinInvalidateRect to invalidate the entire
  18623.  client window and generate a WM_PAINT message. During the WM_PAINT message,
  18624.  BEEPER1 uses the fFlipFlop variable to determine the color (blue or red)
  18625.  used to paint the client window:
  18626.  
  18627.    WinQueryWindowRect (hwnd, &rcl) ;
  18628.    WinFillRect (hps, &rcl, fFlipFlop ? CLR_BLUE: CLR_RED) ;
  18629.  
  18630.  The window procedure receives WM_TIMER messages during the entire time the
  18631.  window exists. Only when BEEPER1 exits the message queue in main on receipt
  18632.  of a WM_QUIT message does the program stop the timer:
  18633.  
  18634.    WinStopTimer (hab, hwndClient, ID_TIMER) ;
  18635.  
  18636.  BEEPER1 then terminates normally.
  18637.  
  18638.  A program doesn't need to start and stop the timer in main. If the program
  18639.  doesn't need a timer for the entire duration of the program, it can start or
  18640.  stop the timer from the window procedure. Although BEEPER1 causes its client
  18641.  window to be repainted once a second by calling WinInvalidateRect, a program
  18642.  can also call WinGetPS to do some painting while processing the WM_TIMER
  18643.  message. The CLOCK program shown later in this chapter paints during the
  18644.  WM_TIMER message.
  18645.  
  18646.  Timer Imprecision
  18647.  If you can tolerate the program's incessant beeping, you can learn a lot
  18648.  about the timer by experimenting with BEEPER1 while running other
  18649.  Presentation Manager programs. The first discovery is that the timer is not
  18650.  a precise and regular clock tick. There are several reasons for this.
  18651.  
  18652.  The resolution of the timer depends on the resolution of the hardware clock
  18653.  in the computer. Under 0S/2, the hardware clock generates an interrupt every
  18654.  31.25 msec, or 32 times per second. The rate of the WM_TIMER messages on a
  18655.  PC is always an integral multiple of 32 msec. You can't receive WM_TIMER
  18656.  messages more frequently than 32 msec.
  18657.  
  18658.  The WM_TIMER message isn't sent directly to the window procedure but is
  18659.  instead placed in the program's message queue. (Actually, the Presentation
  18660.  Manager handles WM_TIMER messages a little differently than it does other
  18661.  queued messages: WM_TIMER messages are not actually placed in the queue.
  18662.  Programs needn't worry about this, however.) WM_TIMER messages are low
  18663.  priority──WinGetMsg retrieves other queued messages (except WM_PAINT)
  18664.  before WM_TIMER messages. There can be a delay between the time the message
  18665.  is placed in the queue and the time the window procedure gets it. However,
  18666.  the Presentation Manager doesn't load a message queue with multiple WM_TIMER
  18667.  messages if the program can't process them. The message queue never contains
  18668.  more than one timer message of a particular timer ID.
  18669.  
  18670.  Limited Availability of Timers
  18671.  The Presentation Manager allows only a limited number of timers to be set
  18672.  throughout the system. (The initial release of the Presentation Manager is
  18673.  limited to 40 timers.) A program can determine how many timers are still
  18674.  available in the Presentation Manager by calling
  18675.  
  18676.    lAvailTimers = WinQuerySysValue (HWND_DESKTOP, SV_CTIMERS) ;
  18677.  
  18678.  However, it's often easier to simply call WinStartTimer. If WinStartTimer
  18679.  returns 0, then no timer was available.
  18680.  
  18681.  If your program can't work properly without a timer (as is obviously the
  18682.  case for a clock application), then the program has no choice but to
  18683.  terminate if no timer is available. You should display a message box
  18684.  informing the user of this problem. If you set the timer in main after the
  18685.  WinCreateStdWindow call, here is one way of dealing with the lack of an
  18686.  available timer:
  18687.  
  18688.    hwndFrame = WinCreateStdWindow (...) ;
  18689.  
  18690.    if (WinStartTimer (hab, hwndClient, ID_TIMER, 1000))
  18691.         {
  18692.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  18693.              WinDispatchMsg (hab, &qmsg) ;
  18694.  
  18695.         WinStopTimer (hab, hwndClient, ID_TIMER) ;
  18696.         }
  18697.    else
  18698.         WinMessageBox (HWND_DESKTOP, hwndClient,
  18699.                        "Too many clocks or timers",
  18700.                        "Program Name", 0, MB_OK | MB_ICONEXCLAMATION) ;
  18701.  
  18702.    WinDestroyWindow (hwndFrame) ;
  18703.  
  18704.  If WinStartTimer returns a nonzero value, the program enters the message
  18705.  loop and later calls WinStopTimer when it exits the message loop. Otherwise,
  18706.  the program displays a message box, destroys the frame window, and
  18707.  terminates normally. You should perform this check in every program you
  18708.  write that uses a timer.
  18709.  
  18710.  A One-Shot Timer
  18711.  In some applications you may not need a timer that repeatedly sends WM_TIMER
  18712.  messages. Instead, you may want to send only one WM_TIMER message after a
  18713.  specified period of time. In this case you can set the timer normally and
  18714.  call WinStopTimer during processing of the WM_TIMER message:
  18715.  
  18716.    case WM_TIMER:
  18717.              [other program lines]
  18718.         WinStopTimer (hab, hwnd, ID_TIMER) ;
  18719.         return 0 ;
  18720.  
  18721.  Calling WinStopTimer not only stops future WM_TIMER messages but also clears
  18722.  the message queue of any pending WM_TIMER messages. You'll never receive a
  18723.  stray WM_TIMER message after you call WinStopTimer.
  18724.  
  18725.  A Timer Over 65-1/2 Seconds
  18726.  The maximum timer interval is 65,535 msec, or 65-1/2 seconds. If you need a
  18727.  timer interval greater than this (for example, 30 minutes), you can first
  18728.  set a static variable that contains the duration in minutes:
  18729.  
  18730.    usMinuteWait = 30 ;
  18731.  
  18732.  You then set a timer for 1 minute:
  18733.  
  18734.    WinStartTimer (hab, hwnd, ID_TIMER, 60000) ;
  18735.  
  18736.  During WM_TIMER processing you decrement and test usMinuteWait:
  18737.  
  18738.    case WM_TIMER:
  18739.         if (--usMinuteWait == 0)
  18740.              {
  18741.                   [other program lines]
  18742.              }
  18743.         return 0 ;
  18744.  
  18745.  An alternative method is to call DosGetDateTime to get the current time when
  18746.  you first start the timer. During the WM_TIMER message you can call
  18747.  DosGetDateTime again to determine if 30 minutes have elapsed.
  18748.  
  18749.  The WinGetCurrentTime function can also be helpful here. This function
  18750.  returns the elapsed time in milliseconds since OS/2 was first booted. This
  18751.  is a ULONG value that rolls over to 0 every 49 days. Let's assume again that
  18752.  you want to set a 30-minute timer interval. First, define a static ULONG
  18753.  variable:
  18754.  
  18755.    static ULONG ulStartTime ;
  18756.  
  18757.  Then call WinGetCurrentTime and WinStartTimer:
  18758.  
  18759.    ulStartTime = WinGetCurrentTime (hab) ;
  18760.    WinStartTimer (hab, hwnd, ID_TIMER, 60000) ;
  18761.  
  18762.  During the WM_TIMER message, check to see if 30 minutes have elapsed:
  18763.  
  18764.    case WM_TIMER:
  18765.         if (WinGetCurrentTime (hab) - ulStartTime > 30 * 60 * 1000)
  18766.              {
  18767.                   [other program lines]
  18768.              }
  18769.         return 0 ;
  18770.  
  18771.  Resetting the Timer Time
  18772.  You may need to change the interval of the WM_TIMER messages. For example,
  18773.  you may have originally set the timer for one-second intervals:
  18774.  
  18775.    WinStartTimer (hab, hwnd, ID_TIMER, 1000) ;
  18776.  
  18777.  If you later need to change that to five-second intervals, you can simply
  18778.  call WinStartTimer again with the same timer ID and a different elapsed
  18779.  time:
  18780.  
  18781.    WinStartTimer (hab, hwnd, ID_TIMER, 5000) ;
  18782.  
  18783.  Using Multiple Timers
  18784.  If you want, you can set multiple timers in your program. Suppose you want
  18785.  one timer for one-second intervals and another timer for one-minute
  18786.  intervals. You first define two IDs:
  18787.  
  18788.    #define ID_SECTIMER 1
  18789.    #define ID_MINTIMER 2
  18790.  
  18791.  To start the timers, make two WinStartTimer calls:
  18792.  
  18793.    WinStartTimer (hab, hwnd, ID_SECTIMER, 1000) ;
  18794.    WinStartTimer (hab, hwnd, ID_MINTIMER, 60000) ;
  18795.  
  18796.  The processing of the WM_TIMER message can use a switch and case
  18797.  construction to do different processing based on the timer ID stored in mp1:
  18798.  
  18799.    case WM_TIMER:
  18800.         switch (SHORT1FROMMP (mp1))
  18801.              {
  18802.              case ID_SECTIMER:
  18803.                        [once-per-second processing]
  18804.                   return 0 ;
  18805.  
  18806.              case ID_MINTIMER:
  18807.                        [once-per-minute processing]
  18808.                   return 0 ;
  18809.              }
  18810.         break ;
  18811.  
  18812.  Before your program terminates, it stops both timers:
  18813.  
  18814.    WinStopTimer (hab, hwnd, ID_SECTIMER) ;
  18815.    WinStopTimer (hab, hwnd, ID_MINTIMER) ;
  18816.  
  18817.  But considering that the Presentation Manager makes available only a limited
  18818.  number of timers, you should feel a little guilty about hogging system
  18819.  resources like this. A better approach is to set only one timer (the one
  18820.  with the shortest interval) and then derive longer intervals from that.
  18821.  
  18822.  The Timers You Don't Set
  18823.  
  18824.  Even if you never call WinStartTimer in your program, WM_TIMER messages may
  18825.  still be posted through your message queue and even dispatched to your
  18826.  client window procedure. Sometimes you need to make special provisions for
  18827.  these messages.
  18828.  
  18829.  You'll recall that the TYPEAWAY program in Chapter 8 creates a blinking
  18830.  cursor. The blink is controlled by a timer. Because the client window
  18831.  procedure in TYPEAWAY doesn't explicitly process WM_TIMER messages, the
  18832.  messages are passed on to WinDefWindowProc. That's where the
  18833.  cursor-blinking logic is. If you add the following lines to TYPEAWAY's
  18834.  client window procedure, the cursor won't blink:
  18835.  
  18836.    case WM_TIMER:
  18837.         return 0 ;
  18838.  
  18839.  
  18840.  Child window scroll bars and edit fields (discussed in Chapters 11 and 14)
  18841.  also use the timer to blink their cursors. If you create a scroll bar or
  18842.  edit window, the WM_TIMER messages come through the program's message queue
  18843.  but are dispatched to the window procedure associated with the child window.
  18844.  
  18845.  If you set a timer in a program that also creates a blinking cursor, you
  18846.  should process only those WM_TIMER messages with the ID number you use (for
  18847.  example, ID_TIMER). All other WM_TIMER messages should be passed on to
  18848.  WinDefWindowProc. The logic looks like this:
  18849.  
  18850.    case WM_TIMER:
  18851.         if (SHORT1FROMMP (mp1) == ID_TIMER)
  18852.              {
  18853.                   [process timer message]
  18854.              return 0 ;
  18855.              }
  18856.         break ;
  18857.  
  18858.  If you set multiple timers, you can use switch and case statements and break
  18859.  for the default case.
  18860.  
  18861.  The IDs for the cursor, scroll bar, and flashing window timers are defined
  18862.  in PMWIN.H using the identifiers TID_CURSOR, TID_SCROLL, and
  18863.  TID_FLASHWINDOW. These are set equal to 0xFFFF, 0xFFFE, and 0xFFFD, so you
  18864.  should avoid using those IDs for any other timers.
  18865.  
  18866.  The Uncommon Method of Using a Timer
  18867.  
  18868.  The examples in all of the preceding sections of this chapter use the
  18869.  following form of the WinStartTimer call:
  18870.  
  18871.    WinStartTimer (hab, hwnd, idTimer, usMsecInterval) ;
  18872.  
  18873.  where idTimer is a predefined constant.
  18874.  
  18875.  The second form of the WinStartTimer function requires that you first define
  18876.  a variable to store the timer ID:
  18877.  
  18878.    USHORT idTimer ;
  18879.  
  18880.  You then call the WinStartTimer function like this:
  18881.  
  18882.    idTimer = WinStartTimer (hab, NULL, 0, usMsecInterval) ;
  18883.  
  18884.  The second parameter (normally set to the window handle) is set to NULL in
  18885.  this form of WinStartTimer. The Presentation Manager ignores the third
  18886.  parameter and instead returns a timer ID (or 0 if no timer was available)
  18887.  from the function. You use this ID when stopping the timer:
  18888.  
  18889.    WinStopTimer (hab, NULL, idTimer) ;
  18890.  
  18891.  This form of WinStartMessage requires that the WM_TIMER message be handled
  18892.  in a special way. Although the message is posted to the message queue
  18893.  associated with the thread, the window handle of the message is set to NULL.
  18894.  This means that the message won't be dispatched to a window procedure.
  18895.  Instead, it must be processed immediately after it is retrieved from the
  18896.  message queue. The BEEPER2 program, shown in Figure 10-2, shows how this is
  18897.  done.
  18898.  
  18899.  Figure 10-2.  The BEEPER2 program.
  18900.  
  18901.    The BEEPER2 File
  18902.  
  18903.    #-------------------
  18904.    # BEEPER2 make file
  18905.    #-------------------
  18906.  
  18907.    beeper2.obj : beeper2.c
  18908.         cl -c -G2sw -W3 beeper2.c
  18909.  
  18910.    beeper2.exe : beeper2.obj beeper2.def
  18911.         link beeper2, /align:16, NUL, os2, beeper2
  18912.  
  18913.    The BEEPER2.C File
  18914.  
  18915.    /*---------------------------------------
  18916.       BEEPER2.C -- Timer Demo Program No. 2
  18917.      ---------------------------------------*/
  18918.  
  18919.    #define INCL_WIN
  18920.    #include <os2.h>
  18921.  
  18922.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  18923.  
  18924.    BOOL fFlipFlop ;
  18925.  
  18926.    int main (void)
  18927.         {
  18928.         static char  szClientClass [] = "Beeper2" ;
  18929.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  18930.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  18931.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  18932.         HAB          hab ;
  18933.         HMQ          hmq ;
  18934.         HWND         hwndFrame, hwndClient ;
  18935.         QMSG         qmsg ;
  18936.         USHORT       idTimer ;
  18937.  
  18938.         hab = WinInitialize (0) ;
  18939.         hmq = WinCreateMsgQueue (hab, 0) ;
  18940.  
  18941.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  18942.  
  18943.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  18944.                                         &flFrameFlags, szClientClass, NULL,
  18945.                                         0L, NULL, 0, &hwndClient) ;
  18946.  
  18947.         idTimer = WinStartTimer (hab, NULL, 0, 1000) ;
  18948.  
  18949.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  18950.              {
  18951.              if (qmsg.msg == WM_TIMER && SHORT1FROMMP (qmsg.mp1) == idTimer)
  18952.                   {
  18953.                   WinAlarm (HWND_DESKTOP, WA_NOTE) ;
  18954.                   fFlipFlop = !fFlipFlop ;
  18955.                   WinInvalidateRect (hwndClient, NULL, FALSE) ;
  18956.                   }
  18957.              else
  18958.                   WinDispatchMsg (hab, &qmsg) ;
  18959.              }
  18960.  
  18961.         WinStopTimer (hab, NULL, idTimer) ;
  18962.  
  18963.         WinDestroyWindow (hwndFrame) ;
  18964.         WinDestroyMsgQueue (hmq) ;
  18965.         WinTerminate (hab) ;
  18966.         return 0 ;
  18967.         }
  18968.  
  18969.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  18970.         {
  18971.         HPS   hps ;
  18972.         RECTL rcl ;
  18973.  
  18974.         switch (msg)
  18975.              {
  18976.              case WM_PAINT:
  18977.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  18978.  
  18979.                   WinQueryWindowRect (hwnd, &rcl) ;
  18980.                   WinFillRect (hps, &rcl, fFlipFlop ? CLR_BLUE : CLR_RED) ;
  18981.  
  18982.                   WinEndPaint (hps) ;
  18983.                   return 0 ;
  18984.              }
  18985.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  18986.         }
  18987.  
  18988.    The BEEPER2.DEF File
  18989.  
  18990.    ;------------------------------------
  18991.    ; BEEPER2.DEF module definition file
  18992.    ;------------------------------------
  18993.  
  18994.    NAME           BEEPER2   WINDOWAPI
  18995.  
  18996.    DESCRIPTION    'Timer Demo Program No. 2 (C) Charles Petzold, 1988'
  18997.    PROTMODE
  18998.    HEAPSIZE       1024
  18999.    STACKSIZE      8192
  19000.    EXPORTS        ClientWndProc
  19001.  
  19002.  
  19003.  BEEPER2 doesn't process the WM_TIMER message in its client window procedure
  19004.  but instead has the timer logic within the message loop, as shown on the
  19005.  next page.
  19006.  
  19007.    while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  19008.         {
  19009.         if (qmsg.msg == WM_TIMER && SHORT1FROMMP (qmsg.mp1) == idTimer)
  19010.              {
  19011.              WinAlarm (HWND_DESKTOP, WA_NOTE) ;
  19012.              fFlipFlop = !fFlipFlop ;
  19013.              WinInvalidateRect (hwndClient, NULL, FALSE) ;
  19014.              }
  19015.         else
  19016.              WinDispatchMsg (hab, &qmsg) ;
  19017.         }
  19018.  
  19019.  BEEPER2 checks to see if the msg field of the QMSG structure is equal to
  19020.  WM_TIMER and if the low USHORT of the mp1 parameter is equal to the timer ID
  19021.  returned from WinStartTimer. If the check is successful, BEEPER2 proceeds
  19022.  like BEEPER1 when it received a WM_TIMER message. If not, BEEPER2 dispatches
  19023.  the message to the window procedure. This form of the WinStartTimer function
  19024.  might be appropriate for a program that creates several threads of execution
  19025.  and needs a timer in a thread that doesn't create any windows.
  19026.  
  19027.  If you move or resize BEEPER2's window, or invoke the system menu, you'll
  19028.  notice that the WM_TIMER messages seemingly stop. These operations involve
  19029.  the use of a different message loop than the one in your program, so any
  19030.  WM_TIMER message in the queue is ignored.
  19031.  
  19032.  
  19033.  Three Timer Programs
  19034.  
  19035.  Now let's put what we've learned into practice by writing three useful
  19036.  programs──a free memory display and two clocks (one digital, one analog).
  19037.  
  19038.  A Free Memory Display
  19039.  
  19040.  The FREEMEM program, shown in Figure 10-3, is the Presentation Manager
  19041.  version of a program that I originally wrote for Microsoft Windows
  19042.  (Programming Windows, Microsoft Press, 1988). Some Windows programmers have
  19043.  found FREEMEM useful as a simple debugging aid. The program creates a tiny
  19044.  window and positions it at the lower-left corner of the display. The window
  19045.  displays, in bytes, the amount of free memory in OS/2. The display is
  19046.  updated every second──that's where the timer helps out.
  19047.  
  19048.  Figure 10-3.  The FREEMEM program.
  19049.  
  19050.    The FREEMEM File
  19051.  
  19052.    #-------------------
  19053.    # FREEMEM make file
  19054.    #-------------------
  19055.  
  19056.    freemem.obj : freemem.c
  19057.         cl -c -G2sw -W3 freemem.c
  19058.  
  19059.    freemem.exe : freemem.obj freemem.def
  19060.         link freemem, /align:16, NUL, os2, freemem
  19061.  
  19062.    The FREEMEM.C File
  19063.  
  19064.    /*----------------------------------
  19065.       FREEMEM.C -- Free Memory Display
  19066.      ----------------------------------*/
  19067.  
  19068.    #define INCL_WIN
  19069.    #define INCL_GPI
  19070.    #define INCL_DOS
  19071.    #include <os2.h>
  19072.    #include <string.h>
  19073.  
  19074.    #define ID_TIMER 1
  19075.  
  19076.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  19077.    VOID    SizeTheWindow (HWND) ;
  19078.  
  19079.    int main (void)
  19080.         {
  19081.         static CHAR  szClientClass[] = "FreeMem" ;
  19082.         static ULONG flFrameFlags = FCF_TITLEBAR | FCF_SYSMENU  |
  19083.                                     FCF_BORDER   | FCF_TASKLIST ;
  19084.         HAB          hab ;
  19085.         HMQ          hmq ;
  19086.         HWND         hwndFrame, hwndClient ;
  19087.         QMSG         qmsg ;
  19088.  
  19089.         hab = WinInitialize (0) ;
  19090.         hmq = WinCreateMsgQueue (hab, 0) ;
  19091.  
  19092.         WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;
  19093.  
  19094.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  19095.                                         &flFrameFlags, szClientClass, NULL,
  19096.                                         0L, NULL, 0, &hwndClient) ;
  19097.         SizeTheWindow (hwndFrame) ;
  19098.  
  19099.         if (WinStartTimer (hab, hwndClient, ID_TIMER, 1000))
  19100.              {
  19101.              while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  19102.                   WinDispatchMsg (hab, &qmsg) ;
  19103.  
  19104.              WinStopTimer (hab, hwndClient, ID_TIMER) ;
  19105.              }
  19106.         else
  19107.              WinMessageBox (HWND_DESKTOP, hwndClient,
  19108.                             "Too many clocks or timers",
  19109.                             szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;
  19110.  
  19111.         WinDestroyWindow (hwndFrame) ;
  19112.         WinDestroyMsgQueue (hmq) ;
  19113.         WinTerminate (hab) ;
  19114.         return 0 ;
  19115.         }
  19116.  
  19117.    VOID SizeTheWindow (HWND hwndFrame)
  19118.         {
  19119.         static CHAR szText [] = "1,234,567,890 bytes" ;
  19120.         HPS         hps ;
  19121.         POINTL      aptl[TXTBOX_COUNT] ;
  19122.         RECTL       rcl ;
  19123.  
  19124.         hps = WinGetPS (hwndFrame) ;
  19125.         GpiQueryTextBox (hps, sizeof szText - 1L, szText, TXTBOX_COUNT, aptl)
  19126.         WinReleasePS (hps) ;
  19127.  
  19128.         rcl.yBottom = 0 ;
  19129.         rcl.yTop    = 3 * (aptl[TXTBOX_TOPLEFT].y -
  19130.                            aptl[TXTBOX_BOTTOMLEFT].y) / 2 ;
  19131.         rcl.xLeft   = 0 ;
  19132.         rcl.xRight  = (sizeof szText + 1L) * (aptl[TXTBOX_BOTTOMRIGHT].x -
  19133.                        aptl[TXTBOX_BOTTOMLEFT].x) / (sizeof szText - 1L) ;
  19134.  
  19135.         WinCalcFrameRect (hwndFrame, &rcl, FALSE) ;
  19136.  
  19137.         WinSetWindowPos (hwndFrame, NULL, (SHORT) rcl.xLeft, (SHORT) rcl.yBott
  19138.                          (SHORT) (rcl.xRight - rcl.xLeft),
  19139.                          (SHORT) (rcl.yTop - rcl.yBottom), SWP_SIZE | SWP_MOVE
  19140.         }
  19141.  
  19142.    VOID FormatNumber (CHAR *pchResult, ULONG ulValue)
  19143.         {
  19144.         BOOL  fDisplay = FALSE ;
  19145.         SHORT sDigit ;
  19146.         ULONG ulQuotient, ulDivisor = 1000000000L ;
  19147.  
  19148.         for (sDigit = 0 ; sDigit < 10 ; sDigit++)
  19149.              {
  19150.              ulQuotient = ulValue / ulDivisor ;
  19151.  
  19152.              if (fDisplay || ulQuotient > 0 || sDigit == 9)
  19153.                   {
  19154.                   fDisplay = TRUE ;
  19155.  
  19156.                   *pchResult++ = (CHAR) ('0' + ulQuotient) ;
  19157.  
  19158.                   if ((sDigit % 3 == 0) && sDigit != 9)
  19159.                        *pchResult++ = ',' ;
  19160.                   }
  19161.              ulValue -= ulQuotient * ulDivisor ;
  19162.              ulDivisor /= 10 ;
  19163.              }
  19164.         *pchResult = '\0' ;
  19165.         }
  19166.  
  19167.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  19168.         {
  19169.         static RECTL rcl ;
  19170.         static ULONG ulFreeMem, ulPrevMem ;
  19171.         CHAR         szBuffer [24] ;
  19172.         HPS          hps;
  19173.  
  19174.         switch (msg)
  19175.              {
  19176.              case WM_SIZE:
  19177.                   WinQueryWindowRect (hwnd, &rcl) ;
  19178.                   return 0 ;
  19179.  
  19180.              case WM_TIMER:
  19181.                   DosMemAvail (&ulFreeMem) ;
  19182.  
  19183.  
  19184.                   if (ulFreeMem != ulPrevMem)
  19185.                        {
  19186.                        WinInvalidateRect (hwnd, NULL, FALSE) ;
  19187.                        ulPrevMem = ulFreeMem ;
  19188.                        }
  19189.                   return 0 ;
  19190.  
  19191.              case WM_PAINT:
  19192.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  19193.  
  19194.                   FormatNumber (szBuffer, ulFreeMem) ;
  19195.                   strcat (szBuffer, " bytes") ;
  19196.  
  19197.                   WinDrawText (hps, -1, szBuffer, &rcl,
  19198.                                CLR_NEUTRAL, CLR_BACKGROUND,
  19199.                                DT_CENTER | DT_VCENTER | DT_ERASERECT) ;
  19200.  
  19201.                   WinEndPaint (hps) ;
  19202.                   return 0 ;
  19203.              }
  19204.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  19205.         }
  19206.  
  19207.    The FREEMEM.DEF File
  19208.  
  19209.    ;------------------------------------
  19210.    ; FREEMEM.DEF module definition file
  19211.    ;------------------------------------
  19212.  
  19213.    NAME           FREEMEM   WINDOWAPI
  19214.  
  19215.    DESCRIPTION    'Free Memory Display (C) Charles Petzold, 1988'
  19216.    PROTMODE
  19217.    HEAPSIZE       1024
  19218.    STACKSIZE      8192
  19219.    EXPORTS        ClientWndProc
  19220.  
  19221.  
  19222.  FREEMEM starts the timer in main and displays a message box if WinStartTimer
  19223.  returns 0. The processing of the WM_TIMER message in ClientWndProc is
  19224.  simple, as shown on the next page.
  19225.  
  19226.    case WM_TIMER:
  19227.         DosMemAvail (&ulFreeMem) ;
  19228.         if (ulFreeMem != ulPrevMem)
  19229.              {
  19230.              WinInvalidateRect (hwnd, NULL, FALSE) ;
  19231.              ulPrevMem = ulFreeMem ;
  19232.              }
  19233.         return 0 ;
  19234.  
  19235.  DosMemAvail is an OS/2 kernel function that returns the size of the largest
  19236.  contiguous block of free memory, which isn't necessarily the same as total
  19237.  free memory. For example, if you specify that the DOS compatibility box is
  19238.  less than 640 KB (or if you run a protected mode-only session), the lower
  19239.  640 KB of memory won't be included in the value reported by DosMemAvail,
  19240.  because that memory isn't contiguous with memory above 1 MB. Nor will
  19241.  DosMemAvail show memory that could become available by swapping or
  19242.  discarding memory segments or by compacting free memory.
  19243.  
  19244.  FREEMEM saves the previous free memory size in ulPrevMem. Only if that size
  19245.  differs from the current value returned from DosMemAvail will FREEMEM
  19246.  invalidate the window to generate a WM_PAINT message. The WM_PAINT
  19247.  processing calls the function FormatNumber to convert the memory size into a
  19248.  text string with comma separators.
  19249.  
  19250.  FREEMEM creates a window of a fixed size positioned in a set area of the
  19251.  display, so it is worthwhile to take a closer look at how this is done. The
  19252.  WinCreateStdWindow function in FREEMEM uses frame creation flags of
  19253.  FCF_TITLEBAR, FCF_SYSMENU, FCF_BORDER, and FCF_TASKLIST. The FCF_SIZEBORDER,
  19254.  FCF_MINMAX, and FCF_SHELLPOSITION flags are not used.
  19255.  
  19256.  Because the window doesn't contain the minimize/maximize menu, the Minimize
  19257.  and Maximize options on the system menu are grayed and disabled.
  19258.  
  19259.  A program that does not use the the FCF_SHELLPOSITION flag when creating the
  19260.  standard window must call WinSetWindowPos to give the frame window a size
  19261.  and position. This is done in FREEMEM's SizeTheWindow function. Because the
  19262.  size of the client window must be based on the size of the text string it
  19263.  displays, the function first calls GpiQueryTextBox for a maximum possible
  19264.  string length. SizeTheWindow then defines the screen coordinates of a RECTL
  19265.  structure that contains the position and size of this client window.
  19266.  
  19267.  The positioning of the client window at the lower-left corner of the screen
  19268.  is indicated by the yBottom and xLeft fields. To allow a little margin
  19269.  around the text, the client window rectangle is set to 1-1/2 times the
  19270.  height of the text box with a width sufficient for the string plus a slight
  19271.  margin.
  19272.  
  19273.  That RECTL structure is the position and size of the client window. The
  19274.  WinCalcFrameRect function converts this rectangle to a frame window position
  19275.  and size:
  19276.  
  19277.    WinCalcFrameRect (hwndFrame, &rcl, FALSE) ;
  19278.  
  19279.  SizeTheWindow can then set the position and size of the frame window:
  19280.  
  19281.    WinSetWindowPos (hwndFrame, NULL, (SHORT) rcl.xLeft, (SHORT) rcl.yBottom,
  19282.                     (SHORT) (rcl.xRight - rcl.xLeft),
  19283.                     (SHORT) (rcl.yTop - rcl.yBottom), SWP_SIZE | SWP_MOVE) ;
  19284.  
  19285.  This window won't be the active window. Because the program's purpose is to
  19286.  display some information, FREEMEM needn't be the active window when it is
  19287.  first displayed. If we wanted FREEMEM to be the active win dow when it is
  19288.  first displayed, we could include SWP_ACTIVATE among the last parameters to
  19289.  WinSetWindowPos.
  19290.  
  19291.  Figure 10-4 shows FREEMEM running in the lower-left corner of the
  19292.  Presentation Manager.
  19293.  
  19294.  A Digital Clock
  19295.  
  19296.  Figure 10-5, shows the DIGCLOCK program, a digital clock that occupies a
  19297.  small window positioned at the lower-right corner of the display. The clock
  19298.  displays the day of the week, the date (month/day/year), and the time. It
  19299.  is updated (with help from the Presentation Manager timer) every second.
  19300.  
  19301.  Figure 10-5.  The DIGCLOCK program.
  19302.  
  19303.    The DIGCLOCK File
  19304.  
  19305.    #--------------------
  19306.    # DIGCLOCK make file
  19307.    #--------------------
  19308.  
  19309.    digclock.obj : digclock.c
  19310.         cl -c -G2sw -W3 digclock.c
  19311.  
  19312.    digclock.exe : digclock.obj digclock.def
  19313.         link digclock, /align:16, NUL, os2, digclock
  19314.  
  19315.    The DIGCLOCK.C File
  19316.  
  19317.    /*-----------------------------
  19318.       DIGCLOCK.C -- Digital Clock
  19319.      -----------------------------*/
  19320.  
  19321.    #define INCL_WIN
  19322.    #define INCL_GPI
  19323.    #define INCL_DOS
  19324.    #include <os2.h>
  19325.    #include <stdio.h>
  19326.  
  19327.    #define ID_TIMER 1
  19328.  
  19329.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  19330.    VOID    SizeTheWindow (HWND) ;
  19331.  
  19332.    int main (void)
  19333.         {
  19334.         static CHAR  szClientClass[] = "DigClock" ;
  19335.         static ULONG flFrameFlags = FCF_TITLEBAR | FCF_SYSMENU  |
  19336.                                     FCF_BORDER   | FCF_TASKLIST ;
  19337.         HAB          hab ;
  19338.         HMQ          hmq ;
  19339.         HWND         hwndFrame, hwndClient ;
  19340.         QMSG         qmsg ;
  19341.  
  19342.         hab = WinInitialize (0) ;
  19343.         hmq = WinCreateMsgQueue (hab, 0) ;
  19344.  
  19345.         WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;
  19346.  
  19347.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  19348.                                         &flFrameFlags, szClientClass, NULL,
  19349.                                         0L, NULL, 0, &hwndClient) ;
  19350.         SizeTheWindow (hwndFrame) ;
  19351.  
  19352.         if (WinStartTimer (hab, hwndClient, ID_TIMER, 1000))
  19353.              {
  19354.              while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  19355.                   WinDispatchMsg (hab, &qmsg) ;
  19356.  
  19357.              WinStopTimer (hab, hwndClient, ID_TIMER) ;
  19358.              }
  19359.         else
  19360.              WinMessageBox (HWND_DESKTOP, hwndClient,
  19361.                             "Too many clocks or timers",
  19362.                             szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;
  19363.  
  19364.         WinDestroyWindow (hwndFrame) ;
  19365.         WinDestroyMsgQueue (hmq) ;
  19366.         WinTerminate (hab) ;
  19367.         return 0 ;
  19368.         }
  19369.  
  19370.    VOID SizeTheWindow (HWND hwndFrame)
  19371.         {
  19372.         FONTMETRICS fm ;
  19373.         HPS         hps ;
  19374.         RECTL       rcl ;
  19375.  
  19376.         hps = WinGetPS (hwndFrame) ;
  19377.         GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  19378.         WinReleasePS (hps) ;
  19379.  
  19380.         rcl.yBottom = 0 ;
  19381.         rcl.yTop    = 11 * fm.lMaxBaselineExt / 4 ;
  19382.         rcl.xRight  = WinQuerySysValue (HWND_DESKTOP, SV_CXSCREEN) ;
  19383.         rcl.xLeft   = rcl.xRight - 16 * fm.lEmInc ;
  19384.  
  19385.         WinCalcFrameRect (hwndFrame, &rcl, FALSE) ;
  19386.  
  19387.         WinSetWindowPos (hwndFrame, NULL, (SHORT) rcl.xLeft, (SHORT) rcl.yBott
  19388.                          (SHORT) (rcl.xRight - rcl.xLeft),
  19389.                          (SHORT) (rcl.yTop - rcl.yBottom), SWP_SIZE | SWP_MOVE
  19390.         }
  19391.  
  19392.    VOID UpdateTime (HWND hwnd, HPS hps)
  19393.         {
  19394.         static BOOL        fHaveCtryInfo = FALSE ;
  19395.         static CHAR        *szDayName [] = { "Sun", "Mon", "Tue", "Wed",
  19396.                                              "Thu", "Fri", "Sat" } ;
  19397.         static CHAR        szDateFormat [] = " %s  %d%s%02d%s%02d " ;
  19398.         static COUNTRYCODE ctryc = { 0, 0 } ;
  19399.         static COUNTRYINFO ctryi ;
  19400.         CHAR               szBuffer [20] ;
  19401.         DATETIME           dt ;
  19402.         RECTL              rcl ;
  19403.         USHORT             usDataLength ;
  19404.  
  19405.                   /*----------------------------------------
  19406.                      Get Country Information, Date, and Time
  19407.                     ----------------------------------------*/
  19408.  
  19409.         if (!fHaveCtryInfo)
  19410.              {
  19411.              DosGetCtryInfo (sizeof ctryi, &ctryc, &ctryi, &usDataLength) ;
  19412.              fHaveCtryInfo = TRUE ;
  19413.              }
  19414.         DosGetDateTime (&dt) ;
  19415.         dt.year %= 100 ;
  19416.  
  19417.                   /*-------------
  19418.                      Format Date
  19419.                     -------------*/
  19420.                                       /*-----------------
  19421.                                          mm/dd/yy format
  19422.                                         -----------------*/
  19423.         if (ctryi.fsDateFmt == 0)
  19424.  
  19425.              sprintf (szBuffer, szDateFormat, szDayName [dt.weekday],
  19426.                                 dt.month, ctryi.szDateSeparator,
  19427.                                 dt.day,   ctryi.szDateSeparator, dt.year) ;
  19428.  
  19429.                                       /*-----------------
  19430.                                          dd/mm/yy format
  19431.                                         -----------------*/
  19432.         else if (ctryi.fsDateFmt == 1)
  19433.  
  19434.              sprintf (szBuffer, szDateFormat, szDayName [dt.weekday],
  19435.                                 dt.day,   ctryi.szDateSeparator,
  19436.                                 dt.month, ctryi.szDateSeparator, dt.year) ;
  19437.  
  19438.                                       /*-----------------
  19439.                                          yy/mm/dd format
  19440.                                         -----------------*/
  19441.         else
  19442.              sprintf (szBuffer, szDateFormat, szDayName [dt.weekday],
  19443.                                 dt.year,  ctryi.szDateSeparator,
  19444.                                 dt.month, ctryi.szDateSeparator, dt.day) ;
  19445.  
  19446.                   /*--------------
  19447.                      Display Date
  19448.                     --------------*/
  19449.  
  19450.         WinQueryWindowRect (hwnd, &rcl) ;
  19451.         rcl.yBottom += 5 * rcl.yTop / 11 ;
  19452.         WinDrawText (hps, -1, szBuffer, &rcl, CLR_NEUTRAL, CLR_BACKGROUND,
  19453.                      DT_CENTER | DT_VCENTER) ;
  19454.  
  19455.                   /*-------------
  19456.                      Format Time
  19457.                     -------------*/
  19458.                                       /*----------------
  19459.                                          12-hour format
  19460.                                         ----------------*/
  19461.         if ((ctryi.fsTimeFmt & 1) == 0)
  19462.  
  19463.              sprintf (szBuffer, " %d%s%02d%s%02d %cm ",
  19464.                                 (dt.hours + 11) % 12 + 1, ctryi.szTimeSeparato
  19465.                                 dt.minutes, ctryi.szTimeSeparator,
  19466.                                 dt.seconds, dt.hours / 12 ? 'p' : 'a') ;
  19467.  
  19468.                                       /*----------------
  19469.                                          24-hour format
  19470.                                         ----------------*/
  19471.         else
  19472.              sprintf (szBuffer, " %02d%s%02d%s%02d ",
  19473.                                 dt.hours,   ctryi.szTimeSeparator,
  19474.                                 dt.minutes, ctryi.szTimeSeparator, dt.seconds)
  19475.  
  19476.                   /*--------------
  19477.                      Display Time
  19478.                     --------------*/
  19479.  
  19480.         WinQueryWindowRect (hwnd, &rcl) ;
  19481.         rcl.yTop -= 5 * rcl.yTop / 11 ;
  19482.         WinDrawText (hps, -1, szBuffer, &rcl, CLR_NEUTRAL, CLR_BACKGROUND,
  19483.                      DT_CENTER | DT_VCENTER) ;
  19484.         }
  19485.  
  19486.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  19487.         {
  19488.         HPS  hps;
  19489.  
  19490.         switch (msg)
  19491.              {
  19492.              case WM_TIMER:
  19493.                   hps = WinGetPS (hwnd) ;
  19494.                   GpiSetBackMix (hps, BM_OVERPAINT) ;
  19495.  
  19496.                   UpdateTime (hwnd, hps) ;
  19497.  
  19498.                   WinReleasePS (hps) ;
  19499.                   return 0 ;
  19500.  
  19501.              case WM_PAINT:
  19502.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  19503.                   GpiErase (hps) ;
  19504.  
  19505.                   UpdateTime (hwnd, hps) ;
  19506.  
  19507.                   WinEndPaint (hps) ;
  19508.                   return 0 ;
  19509.              }
  19510.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  19511.         }
  19512.  
  19513.  
  19514.    The DIGCLOCK.DEF File
  19515.  
  19516.    ;-------------------------------------
  19517.    ; DIGCLOCK.DEF module definition file
  19518.    ;-------------------------------------
  19519.  
  19520.    NAME           DIGCLOCK  WINDOWAPI
  19521.  
  19522.    DESCRIPTION    'Digital Clock (C) Charles Petzold, 1988'
  19523.    PROTMODE
  19524.    HEAPSIZE       1024
  19525.    STACKSIZE      8192
  19526.    EXPORTS        ClientWndProc
  19527.  
  19528.  
  19529.  To position and size the window, DIGCLOCK uses a technique similar to that
  19530.  used in FREEMEM. To allow a little margin around the two lines of text, I
  19531.  made the client window 2-3/4 times the height and 16 times the width of an
  19532.  average system font uppercase letter:
  19533.  
  19534.    rcl.yBottom = 0 ;
  19535.    rcl.yTop    = 11 * fm.lMaxBaselineExt / 4 ;
  19536.    rcl.xRight  = WinQuerySysValue (HWND_DESKTOP, SV_CXSCREEN) ;
  19537.    rcl.xLeft   = rcl.xRight - 16 * fm.lEmInc ;
  19538.  
  19539.  DIGCLOCK processes its WM_TIMER message by invalidating the client window.
  19540.  The WM_PAINT message calls the UpdateTime function to display the date and
  19541.  time. UpdateTime makes use of two OS/2 kernel functions──DosGetDateTime to
  19542.  obtain the date and time and DosGetCtryInfo to obtain information about the
  19543.  format of the date and time applicable for the country specified in the
  19544.  user's CONFIG.SYS file. Thus the format of the date and time in DIGCLOCK
  19545.  looks much like the format used in the OS/2 DATE, TIME, and DIR commands.
  19546.  The UpdateTime function is mostly a collec tion of various sprintf
  19547.  statements that format the date and time for display. The function writes
  19548.  the two lines of text to its client window using WinDrawText.
  19549.  
  19550.  Figure 10-6 shows DIGCLOCK running in the lower-right corner of the
  19551.  Presentation Manager.
  19552.  
  19553.  An Analog Clock
  19554.  
  19555.  An analog clock program doesn't have to worry about different date and time
  19556.  formats, but the complexity of the graphics more than outweighs that
  19557.  convenience. The analog CLOCK program is shown in Figure 10-7. Most of the
  19558.  code in this program is devoted to displaying the face and hands of the
  19559.  clock, so that's what I'll discuss in this section.
  19560.  
  19561.  Figure 10-7.  The CLOCK program.
  19562.  
  19563.    The CLOCK File
  19564.  
  19565.    #-----------------
  19566.    # CLOCK make file
  19567.    #-----------------
  19568.  
  19569.    clock.obj : clock.c
  19570.         cl -c -G2sw -W3 clock.c
  19571.  
  19572.    clock.exe : clock.obj clock.def
  19573.         link clock, /align:16, NUL, os2, clock
  19574.  
  19575.    The CLOCK.C File
  19576.  
  19577.    /*-------------------------
  19578.       CLOCK.C -- Analog Clock
  19579.      -------------------------*/
  19580.  
  19581.    #define INCL_WIN
  19582.    #define INCL_GPI
  19583.    #include <os2.h>
  19584.    #include <stdlib.h>
  19585.  
  19586.    #define ID_TIMER 1
  19587.  
  19588.    typedef struct
  19589.         {
  19590.         SHORT cxClient ;
  19591.         SHORT cyClient ;
  19592.         SHORT cxPixelDiam ;
  19593.  
  19594.         SHORT cyPixelDiam ;
  19595.         }
  19596.         WINDOWINFO ;
  19597.  
  19598.    typedef WINDOWINFO *PWINDOWINFO ;
  19599.  
  19600.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  19601.  
  19602.    int main (void)
  19603.         {
  19604.         static CHAR  szClientClass[] = "Clock" ;
  19605.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  19606.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  19607.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  19608.         HAB          hab ;
  19609.         HMQ          hmq ;
  19610.         HWND         hwndFrame, hwndClient ;
  19611.         QMSG         qmsg ;
  19612.  
  19613.         hab = WinInitialize (0) ;
  19614.         hmq = WinCreateMsgQueue (hab, 0) ;
  19615.  
  19616.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  19617.  
  19618.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  19619.                                         &flFrameFlags, szClientClass, NULL,
  19620.                                         0L, NULL, 0, &hwndClient) ;
  19621.  
  19622.         if (WinStartTimer (hab, hwndClient, ID_TIMER, 1000))
  19623.              {
  19624.              while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  19625.                   WinDispatchMsg (hab, &qmsg) ;
  19626.  
  19627.              WinStopTimer (hab, hwndClient, ID_TIMER) ;
  19628.              }
  19629.         else
  19630.              WinMessageBox (HWND_DESKTOP, hwndClient,
  19631.                             "Too many clocks or timers",
  19632.                             szClientClass, 0, MB_OK | MB_ICONEXCLAMATION) ;
  19633.  
  19634.         WinDestroyWindow (hwndFrame) ;
  19635.         WinDestroyMsgQueue (hmq) ;
  19636.         WinTerminate (hab) ;
  19637.         return 0 ;
  19638.         }
  19639.  
  19640.    VOID RotatePoint (POINTL aptl[], SHORT sNum, SHORT sAngle)
  19641.         {
  19642.         static SHORT sSin [60] =
  19643.                        {
  19644.                           0,  105,  208,  309,  407,  500,  588,  669,  743,
  19645.                         866,  914,  951,  978,  995, 1000,  995,  978,  951,
  19646.                         866,  809,  743,  669,  588,  500,  407,  309,  208,
  19647.                           0, -104, -207, -308, -406, -499, -587, -668, -742, -
  19648.                        -865, -913, -950, -977, -994, -999, -994, -977, -950, -
  19649.                        -865, -808, -742, -668, -587, -499, -406, -308, -207, -
  19650.                        } ;
  19651.         POINTL       ptlTemp ;
  19652.         SHORT        sIndex ;
  19653.         for (sIndex = 0 ; sIndex < sNum ; sIndex++)
  19654.              {
  19655.              ptlTemp.x = (aptl[sIndex].x * sSin [(sAngle + 15) % 60] +
  19656.                           aptl[sIndex].y * sSin [sAngle]) / 1000 ;
  19657.  
  19658.              ptlTemp.y = (aptl[sIndex].y * sSin [(sAngle + 15) % 60] -
  19659.                           aptl[sIndex].x * sSin [sAngle]) / 1000 ;
  19660.  
  19661.              aptl[sIndex] = ptlTemp ;
  19662.              }
  19663.         }
  19664.  
  19665.    VOID ScalePoint (POINTL aptl[], SHORT sNum, PWINDOWINFO pwi)
  19666.         {
  19667.         SHORT sIndex ;
  19668.  
  19669.         for (sIndex = 0 ; sIndex < sNum ; sIndex++)
  19670.              {
  19671.              aptl[sIndex].x = aptl[sIndex].x * pwi->cxPixelDiam / 200 ;
  19672.              aptl[sIndex].y = aptl[sIndex].y * pwi->cyPixelDiam / 200 ;
  19673.              }
  19674.         }
  19675.  
  19676.    VOID TranslatePoint (POINTL aptl[], SHORT sNum, PWINDOWINFO pwi)
  19677.         {
  19678.         SHORT sIndex ;
  19679.  
  19680.         for (sIndex = 0 ; sIndex < sNum ; sIndex++)
  19681.              {
  19682.              aptl[sIndex].x += pwi->cxClient / 2 ;
  19683.              aptl[sIndex].y += pwi->cyClient / 2 ;
  19684.              }
  19685.         }
  19686.  
  19687.    VOID DrawHand (HPS hps, POINTL aptlIn[], SHORT sNum, SHORT sAngle,
  19688.                   PWINDOWINFO pwi)
  19689.         {
  19690.         POINTL aptl [5] ;
  19691.         SHORT  sIndex ;
  19692.  
  19693.         for (sIndex = 0 ; sIndex < sNum ; sIndex++)
  19694.              aptl [sIndex] = aptlIn [sIndex] ;
  19695.  
  19696.         RotatePoint    (aptl, sNum, sAngle) ;
  19697.         ScalePoint     (aptl, sNum, pwi) ;
  19698.         TranslatePoint (aptl, sNum, pwi) ;
  19699.  
  19700.         GpiMove (hps, aptl) ;
  19701.         GpiPolyLine (hps, sNum - 1L, aptl + 1) ;
  19702.         }
  19703.  
  19704.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  19705.         {
  19706.         static DATETIME   dtPrevious ;
  19707.         static HDC        hdc ;
  19708.         static LONG       xPixelsPerMeter, yPixelsPerMeter ;
  19709.         static POINTL     aptlHour   [5] = { 0,-15, 10,0, 0,60, -10,0, 0,-15 }
  19710.                           aptlMinute [5] = { 0,-20,  5,0, 0,80,  -5,0, 0,-20 }
  19711.                           aptlSecond [2] = { 0,  0,  0,80 } ;
  19712.         static WINDOWINFO wi ;
  19713.         DATETIME          dt ;
  19714.         HPS               hps ;
  19715.         POINTL            aptl [3] ;
  19716.         SHORT             sDiamMM, sAngle ;
  19717.  
  19718.         switch (msg)
  19719.              {
  19720.              case WM_CREATE:
  19721.                   hdc = WinOpenWindowDC (hwnd) ;
  19722.  
  19723.                   DevQueryCaps (hdc, CAPS_VERTICAL_RESOLUTION,
  19724.                                      1L, &yPixelsPerMeter) ;
  19725.  
  19726.                   DevQueryCaps (hdc, CAPS_HORIZONTAL_RESOLUTION,
  19727.                                      1L, &xPixelsPerMeter) ;
  19728.  
  19729.                   DosGetDateTime (&dtPrevious) ;
  19730.                   dtPrevious.hours = (dtPrevious.hours * 5) % 60 +
  19731.                                       dtPrevious.minutes / 12 ;
  19732.                   return 0 ;
  19733.  
  19734.              case WM_SIZE:
  19735.                   wi.cxClient = SHORT1FROMMP (mp2) ;
  19736.                   wi.cyClient = SHORT2FROMMP (mp2) ;
  19737.  
  19738.                   sDiamMM = (SHORT) min (wi.cxClient * 1000L / xPixelsPerMeter
  19739.                                          wi.cyClient * 1000L / yPixelsPerMeter
  19740.  
  19741.                   wi.cxPixelDiam = (SHORT) (xPixelsPerMeter * sDiamMM / 1000)
  19742.                   wi.cyPixelDiam = (SHORT) (yPixelsPerMeter * sDiamMM / 1000)
  19743.                   return 0 ;
  19744.  
  19745.              case WM_TIMER:
  19746.                   DosGetDateTime (&dt) ;
  19747.                   dt.hours = (dt.hours * 5) % 60 + dt.minutes / 12 ;
  19748.  
  19749.                   hps = WinGetPS (hwnd) ;
  19750.                   GpiSetColor (hps, CLR_BACKGROUND) ;
  19751.  
  19752.                   DrawHand (hps, aptlSecond, 2, dtPrevious.seconds, &wi) ;
  19753.  
  19754.                   if (dt.hours   != dtPrevious.hours ||
  19755.                       dt.minutes != dtPrevious.minutes)
  19756.                        {
  19757.                        DrawHand (hps, aptlHour,   5, dtPrevious.hours,   &wi)
  19758.                        DrawHand (hps, aptlMinute, 5, dtPrevious.minutes, &wi)
  19759.                        }
  19760.  
  19761.                   GpiSetColor (hps, CLR_NEUTRAL) ;
  19762.  
  19763.                   DrawHand (hps, aptlHour,   5, dt.hours,   &wi) ;
  19764.                   DrawHand (hps, aptlMinute, 5, dt.minutes, &wi) ;
  19765.                   DrawHand (hps, aptlSecond, 2, dt.seconds, &wi) ;
  19766.  
  19767.                   WinReleasePS (hps) ;
  19768.                   dtPrevious = dt ;
  19769.                   return 0 ;
  19770.  
  19771.              case WM_PAINT:
  19772.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  19773.                   GpiErase (hps) ;
  19774.  
  19775.                   for (sAngle = 0 ; sAngle < 60 ; sAngle++)
  19776.                        {
  19777.                        aptl[0].x = 0 ;
  19778.                        aptl[0].y = 90 ;
  19779.  
  19780.                        RotatePoint    (aptl, 1, sAngle) ;
  19781.                        ScalePoint     (aptl, 1, &wi) ;
  19782.                        TranslatePoint (aptl, 1, &wi) ;
  19783.  
  19784.                        aptl[2].x = aptl[2].y = sAngle % 5 ? 2 : 10 ;
  19785.  
  19786.                        ScalePoint (aptl + 2, 1, &wi) ;
  19787.  
  19788.                        aptl[0].x -= aptl[2].x / 2 ;
  19789.                        aptl[0].y -= aptl[2].y / 2 ;
  19790.  
  19791.                        aptl[1].x = aptl[0].x + aptl[2].x ;
  19792.                        aptl[1].y = aptl[0].y + aptl[2].y ;
  19793.  
  19794.                        GpiMove (hps, aptl) ;
  19795.                        GpiBox (hps, DRO_OUTLINEFILL, aptl + 1,
  19796.                                     aptl[2].x, aptl[2].y) ;
  19797.                        }
  19798.                   DrawHand (hps, aptlHour,   5, dtPrevious.hours,   &wi) ;
  19799.                   DrawHand (hps, aptlMinute, 5, dtPrevious.minutes, &wi) ;
  19800.                   DrawHand (hps, aptlSecond, 2, dtPrevious.seconds, &wi) ;
  19801.  
  19802.                   WinEndPaint (hps) ;
  19803.                   return 0 ;
  19804.              }
  19805.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  19806.         }
  19807.  
  19808.  
  19809.    The CLOCK.DEF File
  19810.  
  19811.    ;----------------------------------
  19812.    ; CLOCK.DEF module definition file
  19813.    ;----------------------------------
  19814.  
  19815.    NAME           CLOCK     WINDOWAPI
  19816.  
  19817.    DESCRIPTION    'Analog Clock (C) Charles Petzold, 1988'
  19818.    PROTMODE
  19819.    HEAPSIZE       1024
  19820.    STACKSIZE      8192
  19821.    EXPORTS        ClientWndProc
  19822.  
  19823.  
  19824.  Figure 10-8 shows CLOCK dominating the full Presentation Manager session.
  19825.  
  19826.  To draw a round clock face, CLOCK defines its own coordinate system. The
  19827.  center of the clock (which is positioned in the center of the client window)
  19828.  is the point (0, 0) in this coordinate system. The horizontal and vertical
  19829.  axes both range from -100 to +100.
  19830.  
  19831.  While processing the WM_CREATE message, CLOCK obtains two values from
  19832.  DevQueryCaps that report the horizontal and vertical resolution of the
  19833.  display in pixels per meter:
  19834.  
  19835.    DevQueryCaps (hdc, CAPS_VERTICAL_RESOLUTION, 1L, &yPixelsPerMeter) ;
  19836.    DevQueryCaps (hdc, CAPS_HORIZONTAL_RESOLUTION, 1L, &xPixelsPerMeter) ;
  19837.  
  19838.  During the WM_SIZE message, the diameter of the clock face in millimeters is
  19839.  calculated based on the width and height of the client window:
  19840.  
  19841.    sDiamMM = (SHORT) min (wi.cxClient * 1000L / xPixelsPerMeter,
  19842.                           wi.cyClient * 1000L / yPixelsPerMeter) ;
  19843.  
  19844.  This value is then converted to a diameter in pixels for both the horizontal
  19845.  and vertical axes:
  19846.  
  19847.    wi.cxPixelDiam = (SHORT) (xPixelsPerMeter * sDiamMM / 1000) ;
  19848.    wi.cyPixelDiam = (SHORT) (yPixelsPerMeter * sDiamMM / 1000) ;
  19849.  
  19850.  As I noted above, CLOCK defines its own coordinates to range from -100 to
  19851.  +100 on the horizontal and vertical axes. Thus, on the horizontal axis, the
  19852.  width in pixels of the clock face is cxPixelDiam, but this corresponds to
  19853.  200 units in CLOCK's coordinate system.
  19854.  
  19855.  CLOCK has two functions to translate one or more POINTL structures from its
  19856.  own coordinate system to the window coordinates used in the GPI functions:
  19857.  ScalePoint and TranslatePoint.
  19858.  
  19859.  ScalePoint uses the relationship between these two coordinate systems to
  19860.  convert a point in CLOCK's coordinate system to pixels:
  19861.  
  19862.    aptl[sIndex].x = aptl[sIndex].x * pwi->cxPixelDiam / 200 ;
  19863.    aptl[sIndex].y = aptl[sIndex].y * pwi->cyPixelDiam / 200 ;
  19864.  
  19865.  CLOCK's coordinate system defines (0, 0) as the center of the client window.
  19866.  The point (0, 0) in window coordinates is the lower-left corner of the
  19867.  window. TranslatePoint converts the point accordingly:
  19868.  
  19869.    aptl[sIndex].x += pwi->cxClient / 2 ;
  19870.    aptl[sIndex].y += pwi->cyClient / 2 ;
  19871.  
  19872.  The more complex aspect of CLOCK involves the rotation of points around the
  19873.  clock face. Let's look at an example. The hour hand of the clock is defined
  19874.  as an array of POINTL structures that specify a starting position and four
  19875.  line segments:
  19876.  
  19877.    static POINTL aptlHour [5] = { 0,-15, 10,0, 0,60, -10,0, 0,-15 }
  19878.  
  19879.  But these are the coordinates only when the hour hand points straight up, at
  19880.  midnight or noon. What are the coordinates of the hour hand at 3:00? To get
  19881.  those coordinates, the points have to be rotated 90 degrees clockwise around
  19882.  a circle. Time for a trigonometry refresher: If the original point is (x, y)
  19883.  and the clockwise angle of rotation is α, then the new point (x', y') is
  19884.  calculated with the following formulas:
  19885.  
  19886.    x' = x COS α + y SIN α
  19887.    y' = y COS α - x SIN α
  19888.  
  19889.  This is done in the RotatePoint function. Because a clock face is divided
  19890.  into 60 increments, all that's needed are 60 sine and cosine values in
  19891.  increments of 6 degrees. The cosines can be derived from the sines by
  19892.  offsetting the angle by 90 degrees.
  19893.  
  19894.  To avoid introducing floating-point math in CLOCK (which would increase the
  19895.  CLOCK.EXE size considerably), the sSin array in RotatePoint contains the 60
  19896.  required sine values scaled by a factor of 1000. The rotation formulas in
  19897.  RotatePoint are
  19898.  
  19899.    ptlTemp.x = (aptl[sIndex].x * sSin [(sAngle + 15) % 60] +
  19900.                 aptl[sIndex].y * sSin [sAngle]) / 1000 ;
  19901.  
  19902.    ptlTemp.y = (aptl[sIndex].y * sSin [(sAngle + 15) % 60] -
  19903.                 aptl[sIndex].x * sSin [sAngle]) / 1000 ;
  19904.  
  19905.    aptl[sIndex] = ptlTemp ;
  19906.  
  19907.  The DrawHand function in CLOCK is passed an array of points that define a
  19908.  clock hand at 12:00. It calls the RotatePoint, ScalePoint, and the
  19909.  TranslatePoint functions to rotate the points and convert them from CLOCK's
  19910.  coordinate system to window coordinates. DrawHand then calls GpiMove and
  19911.  GpiPolyLine to draw the hand. During processing of the WM_PAINT message,
  19912.  CLOCK draws the face of the clock and the three hands at the current time.
  19913.  Processing during the WM_TIMER message updates the position of the clock
  19914.  hands based on the new time obtained from DosGetDateTime.
  19915.  
  19916.  
  19917.  Chapter 11  Control Windows: Putting the Children to Work
  19918.  ───────────────────────────────────────────────────────────────────────────
  19919.  
  19920.  
  19921.  Control windows (sometimes called "child window controls" or simply
  19922.  "controls") are child windows that take the form of objects such as buttons,
  19923.  scroll bars, list boxes, and text entry fields. A control window processes
  19924.  mouse and keyboard input and notifies its owner of significant input events.
  19925.  Although the input originates with the keyboard and the mouse, it is
  19926.  filtered through the control, so you can treat control windows as additional
  19927.  means of input to your program.
  19928.  
  19929.  For example, in a spreadsheet program you might want to display a small push
  19930.  button labeled "Recalculate" on your client window. You can do this in one
  19931.  of two ways. The first way requires the program itself to draw the push
  19932.  button on the client window. The client window procedure then has to process
  19933.  mouse messages and do some hit-testing to determine when the user clicks on
  19934.  the push button. But an easier approach is to create a push button control
  19935.  window that is a child of your client window. The window procedure for the
  19936.  push button window is inside the Presentation Manager. That window procedure
  19937.  draws the button, processes the mouse messages, and sends your client window
  19938.  a message when the button is clicked. By putting child windows to work, your
  19939.  program can delegate the drawing and the mouse hit-testing jobs.
  19940.  
  19941.  We've already explored some of the concepts involved in creating and using
  19942.  control windows. The WELCOME4 program in Chapter 3 created a push button,
  19943.  scroll bar, and text-entry field based on preregistered window classes.
  19944.  Creating each control window required only one WinCreateWindow call. (The
  19945.  only problem was that WELCOME4 didn't know quite what to do with these
  19946.  control windows after it created them.)
  19947.  
  19948.  Although the control windows in WELCOME4 were based on preregistered window
  19949.  classes, you can also create your own classes of control windows. For
  19950.  example, the CHECKER3 program in Chapter 9 created 25 child windows on the
  19951.  surface of its client. These child windows processed mouse clicks by drawing
  19952.  or erasing an X mark on the child window. The child windows added a layer of
  19953.  processing between the user and CHECKER3's client window that simplified
  19954.  mouse input processing.
  19955.  
  19956.  CHECKER3's client window was ignorant of the state (X or no X) of each of
  19957.  the 25 child windows. But it's not difficult to imagine each of the child
  19958.  windows sending messages to the client window whenever the child window was
  19959.  checked or unchecked. We might also have added a facility that allowed the
  19960.  client window to send the child window a message requesting information
  19961.  about the state of a particular rectangle. Had we done this in CHECKER3, the
  19962.  child windows would have been sophisticated enough to qualify as control
  19963.  windows.
  19964.  
  19965.  Control windows appear most often in dialog boxes. You'll discover in
  19966.  Chapter 14 that defining the position and size of control windows in a
  19967.  dialog box is simplified by using a dialog box template. The dialog box
  19968.  logic within the Presentation Manager also assists greatly in much of the
  19969.  overhead involved with using controls, including shifting the keyboard input
  19970.  focus between the windows. However, it's a good exercise to create a few
  19971.  control windows yourself to get a better understanding of dialog boxes and a
  19972.  greater appreciation for the work the Presentation Manager assumes when you
  19973.  use dialog boxes.
  19974.  
  19975.  
  19976.  Control Window Basics
  19977.  
  19978.  Using control windows involves three major jobs:
  19979.  
  19980.    ■  You create a control window by calling WinCreateWindow. Most often, the
  19981.       window class has been preregistered by the Presentation Manager, which
  19982.       means that the window procedure for the class is in the Presentation
  19983.       Manager PMWIN.DLL dynamic link library. You specify the style,
  19984.       position, and size of the control window, and WinCreateWindow returns a
  19985.       handle to the window. The program can later adjust the position and
  19986.       size of the control by calling the WinSetWindowPos function.
  19987.  
  19988.    ■  Your program can send messages to the control window using WinSendMsg.
  19989.       These messages can either set the state of a control or query the
  19990.       current state. The identifiers for the messages you send to controls
  19991.       begin with a prefix that indicates the type of control window that
  19992.       responds to the message. For example, messages that begin with BM are
  19993.       messages you send to button controls, and messages that begin with SBM
  19994.       are messages you send to scroll-bar controls.
  19995.  
  19996.    ■  You receive notification messages from the control window when a
  19997.       significant input event occurs. This usually results from the user
  19998.       clicking on the control window with the mouse or──if the control
  19999.       window has the input focus──pressing a key that affects the control.
  20000.       The notification messages are usually WM_COMMAND and WM_CONTROL
  20001.       messages for most control windows and WM_VSCROLL and WM_HSCROLL
  20002.       messages for scroll bars.
  20003.  
  20004.  Creating the Window
  20005.  
  20006.  You create a control window by calling the WinCreateWindow function, which
  20007.  generally looks like this:
  20008.  
  20009.    hwnd = WinCreateWindow (
  20010.                        hwndParent,         // Parent window
  20011.                        szClass,            // Window class
  20012.                        szText,             // Text
  20013.                        WS_ ...,            // Window style
  20014.                        xPosition,          // Position
  20015.                        yPosition,
  20016.                        cxWidth,            // Width
  20017.                        cyHeight,           // Height
  20018.                        hwndOwner,          // Owner window
  20019.                        hwndPlacement,      // Placement
  20020.                        id,                 // Child ID
  20021.                        pCtrlData,          // Ctrl data
  20022.                        pPresParams) ;      // Pres params
  20023.  
  20024.  When you create a control window based on a preregistered window class, the
  20025.  last two parameters (far pointers to control data and presentation
  20026.  parameters) are often set to NULL. The other parameters are described in the
  20027.  following paragraphs.
  20028.  
  20029.  The Predefined Window Classes
  20030.  
  20031.  In the CHECKER3 program in Chapter 9, the window class parameter in
  20032.  WinCreateWindow was a text string identifying a window class that the
  20033.  program registered. For control windows based on a preregistered window
  20034.  class, this parameter is an identifier beginning with the letters WC. These
  20035.  identifiers are as follows:
  20036.  
  20037.     Preregistered Window Class Type of Window
  20038.     WC_FRAME                   Standard frame window
  20039.     WC_BUTTON                  Push button, check box, and so forth
  20040.     WC_MENU                    Menu (including system menu and
  20041.                                minimize/maximize menu)
  20042.     WC_STATIC                  Static text string and rectangle
  20043.     WC_ENTRYFIELD              Text entry field
  20044.     WC_LISTBOX                 List box
  20045.     WC_SCROLLBAR               Scroll bar
  20046.     WC_TITLEBAR                Standard title bar
  20047.  
  20048.  The WC_FRAME identifier isn't commonly used in the WinCreateWindow function
  20049.  because WinCreateStdWindow creates a frame window. The WC_MENU and
  20050.  WC_TITLEBAR identifiers refer to windows that are usually part of the
  20051.  standard window created with WinCreateStdWindow. Excluding those identifiers
  20052.  leaves us with the five most common control window classes, which are
  20053.  WC_BUTTON, WC_STATIC, WC_ENTRYFIELD, WC_LISTBOX, and WC_SCROLLBAR. The
  20054.  sample programs throughout this chapter create controls of the WC_BUTTON,
  20055.  WC_STATIC, and WC_SCROLLBAR classes.
  20056.  
  20057.  The Window Style
  20058.  
  20059.  The window style parameter of WinCreateWindow is one or more identifiers
  20060.  that define the appearance and functionality of the window. The style
  20061.  identifiers you use depend on the window class. For example, when creating a
  20062.  scroll-bar control window, you specify either SBS_VERT or SBS_HORZ,
  20063.  depending on whether you want a vertical or horizontal scroll bar. When you
  20064.  create a button control, the window style identifies the button as a push
  20065.  button, a radio button, or a check box. The identifier WS_VISIBLE usually is
  20066.  included in the window style. If you omit it, the window is created but not
  20067.  displayed. You must later call WinShowWindow to display the window.
  20068.  
  20069.  Some control windows (such as buttons) display text, which you specify in
  20070.  the text parameter to WinCreateWindow. You can later change the text using
  20071.  the WinSetWindowText function. The position parameters give the coordinates
  20072.  of the lower-left corner of the control relative to the lower-left corner of
  20073.  its parent window. The size parameters specify the control's width and
  20074.  height. You can change the position and size using the WinSetWindowPos
  20075.  function.
  20076.  
  20077.  The Owner and the Parent
  20078.  
  20079.  When you create a child window, you assign it both a parent window and an
  20080.  owner window. The parent window determines where the control is positioned.
  20081.  The position parameters in WinCreateWindow specify the coordinates of the
  20082.  control window relative to the lower-left corner of the control's parent. If
  20083.  the parent window is moved, the child window is moved also. Like all child
  20084.  windows, a control window is clipped on the surface of its parent. It can't
  20085.  appear outside the area its parent occupies.
  20086.  
  20087.  The control window sends notification messages not to its parent but to its
  20088.  owner. The window procedure associated with the owner window is responsible
  20089.  for interpreting these notification messages. Usually, the same window
  20090.  serves as both the parent and the owner of the control. For example, if you
  20091.  create a control window on the surface of your client window, the client
  20092.  window is usually both the parent and the owner of the control window. You
  20093.  can specify a different parent and owner if you want the notification
  20094.  messages to be processed by a window other than the one on which the control
  20095.  is located.
  20096.  
  20097.  A third window handle can be passed to the WinCreateWindow function to
  20098.  specify how overlapping siblings appear on the screen. (This is iden tified
  20099.  as hwndPlacement in the WinCreateWindow call on page 475.) This parameter
  20100.  must be either a window handle of a sibling, HWND_TOP, or HWND_BOTTOM. The
  20101.  terminology often becomes confusing: An HWND_BOTTOM window obscures an
  20102.  HWND_TOP window if the two windows overlap. If you specify a handle of a
  20103.  sibling window, that sibling will be obscured by the new window if the
  20104.  windows overlap. If you create several sibling windows using HWND_TOP, the
  20105.  most recently created window will be obscured by the others. Specifying
  20106.  HWND_BOTTOM for several siblings causes the most recently created window to
  20107.  obscure the siblings that it overlaps.
  20108.  
  20109.  If your child windows do not overlap, you can use either HWND_TOP or
  20110.  HWND_BOTTOM for all of them.
  20111.  
  20112.  The Child ID
  20113.  
  20114.  The child ID is a very important parameter of the WinCreateWindow function.
  20115.  This ID number should be unique for each child of a particular window. The
  20116.  control window uses the ID to identify itself when it sends the owner a
  20117.  notification message. You can use any number you want for a child ID, but
  20118.  it's safest to use numbers less than 32,768 so as not to conflict with
  20119.  predefined IDs used by the frame window. If you create many control windows,
  20120.  you should choose IDs that let you conveniently determine which control is
  20121.  sending you a notification message and what you do with information from the
  20122.  control. For example, the sample programs in this chapter often use the IDs
  20123.  as indexes to arrays.
  20124.  
  20125.  Although the WinCreateWindow function returns a handle to the child window,
  20126.  it's not essential that you save it. You can always determine the child
  20127.  window handle from the child ID by using the following function:
  20128.  
  20129.    hwndChild = WinWindowFromID (hwnd, id) ;
  20130.  
  20131.  The hwnd parameter is the window handle of the parent of hwndChild. The id
  20132.  parameter is the ID you specify when creating the child window.
  20133.  
  20134.  Knowing the handle of a child window, you can also obtain the ID:
  20135.  
  20136.    id = WinQueryWindowUShort (hwndChild, QWS_ID) ;
  20137.  
  20138.  
  20139.  The Button Class
  20140.  
  20141.  Let's begin with buttons, which are almost the simplest type of control
  20142.  window. (Static control windows are actually simpler because they don't
  20143.  process input at all.) When you create a button control window, you specify
  20144.  the WC_BUTTON window class in the WinCreateWindow func tion. The window
  20145.  style indicates the type of button. The most common button window styles are
  20146.  BS_PUSHBUTTON, BS_CHECKBOX, and BS_RADIOBUTTON.
  20147.  
  20148.  A push button is a rounded rectangle that contains text. When you click on
  20149.  the button with the mouse or──if the button has the input focus──press
  20150.  the Spacebar, the button flashes and sends a notification message to its
  20151.  owner. Push buttons generally signal simple actions: "Do this."
  20152.  
  20153.  A check box is a small square (about the height of a character) followed by
  20154.  a text string. Clicking the button with the mouse causes an X to appear in
  20155.  the box; clicking it again removes the X. A program often uses check boxes
  20156.  for various program options.
  20157.  
  20158.  A radio button is a small circle followed by text. Like a check box, a radio
  20159.  button can be either checked or unchecked. Clicking on the radio button
  20160.  checks it, but clicking again doesn't uncheck it. Generally, a group of
  20161.  radio buttons is used to indicate mutually exclusive options. When the user
  20162.  checks one button, the program unchecks all the other buttons in the same
  20163.  group, just as the buttons on a car radio do.
  20164.  
  20165.  A Push Button Demonstration Program
  20166.  
  20167.  The BUTTONS1 program, shown in Figure 11-1, creates two push buttons
  20168.  labeled "Smaller" and "Larger." These buttons appear in the center of the
  20169.  client window. When you click with the mouse on the button labeled
  20170.  "Smaller," the program's window decreases in size by 10 percent. When you
  20171.  click on "Larger," the window size increases by 10 percent.
  20172.  
  20173.  Figure 11-1.  The BUTTONS1 program.
  20174.  
  20175.    The BUTTONS1 File
  20176.  
  20177.    #--------------------
  20178.    # BUTTONS1 make file
  20179.    #--------------------
  20180.  
  20181.    buttons1.obj : buttons1.c
  20182.         cl -c -G2sw -W3 buttons1.c
  20183.  
  20184.    buttons1.exe : buttons1.obj buttons1.def
  20185.         link buttons1, /align:16, NUL, os2, buttons1
  20186.  
  20187.    The BUTTONS1.C File
  20188.  
  20189.    /*-----------------------------------------
  20190.       BUTTONS1.C -- Push Button Demonstration
  20191.      -----------------------------------------*/
  20192.  
  20193.    #define INCL_WIN
  20194.    #define INCL_GPI
  20195.    #include <os2.h>
  20196.  
  20197.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  20198.  
  20199.    int main (void)
  20200.         {
  20201.         static CHAR  szClientClass[] = "Buttons1" ;
  20202.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  20203.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  20204.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  20205.         HAB          hab ;
  20206.         HMQ          hmq ;
  20207.         HWND         hwndFrame, hwndClient ;
  20208.         QMSG         qmsg ;
  20209.  
  20210.    hab = WinInitialize (0) ;
  20211.         hmq = WinCreateMsgQueue (hab, 0) ;
  20212.  
  20213.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  20214.  
  20215.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  20216.                                         &flFrameFlags, szClientClass, NULL,
  20217.                                         0L, NULL, 0, &hwndClient) ;
  20218.  
  20219.         WinSendMsg (hwndFrame, WM_SETICON,
  20220.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  20221.                     NULL) ;
  20222.  
  20223.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  20224.              WinDispatchMsg (hab, &qmsg) ;
  20225.  
  20226.         WinDestroyWindow (hwndFrame) ;
  20227.         WinDestroyMsgQueue (hmq) ;
  20228.         WinTerminate (hab) ;
  20229.         return 0 ;
  20230.         }
  20231.  
  20232.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  20233.         {
  20234.         static CHAR  *szButtonLabel[] = { "Smaller", "Larger" } ;
  20235.         static HWND  hwndFrame, hwndButton[2] ;
  20236.         static SHORT cxClient, cyClient, cxChar, cyChar ;
  20237.         FONTMETRICS  fm ;
  20238.         HPS          hps ;
  20239.         SHORT        id ;
  20240.         RECTL        rcl ;
  20241.  
  20242.         switch (msg)
  20243.              {
  20244.              case WM_CREATE :
  20245.                   hwndFrame = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;
  20246.  
  20247.                   hps = WinGetPS (hwnd) ;
  20248.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  20249.                   cxChar = (SHORT) fm.lAveCharWidth ;
  20250.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  20251.                   WinReleasePS (hps) ;
  20252.  
  20253.                   for (id = 0 ; id < 2 ; id++)
  20254.                        hwndButton[id] = WinCreateWindow (
  20255.                                            hwnd,               // Parent
  20256.                                            WC_BUTTON,          // Class
  20257.                                            szButtonLabel[id],  // Text
  20258.                                            WS_VISIBLE |        // Style
  20259.                                                 BS_PUSHBUTTON,
  20260.                                            0, 0,               // Position
  20261.                                            12 * cxChar,        // Width
  20262.                                            2 * cyChar,         // Height
  20263.                                            hwnd,               // Owner
  20264.                                            HWND_BOTTOM,        // Placement
  20265.                                            id,                 // ID
  20266.                                            NULL,               // Ctrl data
  20267.                                            NULL) ;             // Pres params
  20268.                   return 0 ;
  20269.  
  20270.              case WM_SIZE :
  20271.                   cxClient = SHORT1FROMMP (mp2) ;
  20272.                   cyClient = SHORT2FROMMP (mp2) ;
  20273.  
  20274.                   for (id = 0 ; id < 2 ; id++)
  20275.                        WinSetWindowPos (hwndButton[id], NULL,
  20276.                                  cxClient / 2 + (14 * id - 13) * cxChar,
  20277.                                  (cyClient - 2 * cyChar) / 2,
  20278.                                  0, 0, SWP_MOVE) ;
  20279.                   return 0 ;
  20280.  
  20281.              case WM_COMMAND:
  20282.                   WinQueryWindowRect (hwnd, &rcl) ;
  20283.                   WinMapWindowPoints (hwnd, HWND_DESKTOP, (PPOINTL) &rcl, 2) ;
  20284.  
  20285.                   switch (COMMANDMSG(&msg)->cmd)               // Child ID
  20286.                        {
  20287.                        case 0:                                 // "Smaller"
  20288.                             rcl.xLeft   += cxClient / 20 ;
  20289.                             rcl.xRight  -= cxClient / 20 ;
  20290.                             rcl.yBottom += cyClient / 20 ;
  20291.                             rcl.yTop    -= cyClient / 20 ;
  20292.                             break ;
  20293.  
  20294.                        case 1:                                 // "Larger"
  20295.                             rcl.xLeft   -= cxClient / 20 ;
  20296.                             rcl.xRight  += cxClient / 20 ;
  20297.                             rcl.yBottom -= cyClient / 20 ;
  20298.                             rcl.yTop    += cyClient / 20 ;
  20299.                             break ;
  20300.                        }
  20301.                   WinCalcFrameRect (hwndFrame, &rcl, FALSE) ;
  20302.  
  20303.                   WinSetWindowPos (hwndFrame, NULL,
  20304.                                    (SHORT) rcl.xLeft, (SHORT) rcl.yBottom,
  20305.                                    (SHORT) rcl.xRight - (SHORT) rcl.xLeft,
  20306.                                    (SHORT) rcl.yTop   - (SHORT) rcl.yBottom,
  20307.                                    SWP_MOVE | SWP_SIZE) ;
  20308.                   return 0 ;
  20309.  
  20310.              case WM_ERASEBACKGROUND:
  20311.                   return 1 ;
  20312.              }
  20313.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  20314.         }
  20315.  
  20316.    The BUTTONS1.DEF File
  20317.  
  20318.    ;-------------------------------------
  20319.    ; BUTTONS1.DEF module definition file
  20320.    ;-------------------------------------
  20321.  
  20322.    NAME           BUTTONS1  WINDOWAPI
  20323.  
  20324.    DESCRIPTION    'Push Button Demo (C) Charles Petzold, 1988'
  20325.    PROTMODE
  20326.    HEAPSIZE       1024
  20327.    STACKSIZE      8192
  20328.    EXPORTS        ClientWndProc
  20329.  
  20330.  
  20331.  Figure 11-2 shows BUTTONS1 running under the Presentation Manager. The
  20332.  "Larger" button is in the process of being triggered by the mouse.
  20333.  
  20334.  BUTTONS1 creates these two push buttons during processing of the WM_CREATE
  20335.  message in ClientWndProc:
  20336.  
  20337.    for (id = 0 ; id < 2 ; id++)
  20338.         hwndButton [id] = WinCreateWindow (
  20339.                              hwnd,                // Parent
  20340.                              WC_BUTTON,           // Class
  20341.                              szButtonLabel [id],  // Text
  20342.                              WS_VISIBLE |         // Style
  20343.                                   BS_PUSHBUTTON,
  20344.                              0, 0,                // Position
  20345.                              12 * cxChar,         // Width
  20346.                              2 * cyChar,          // Height
  20347.                              hwnd,                // Owner
  20348.                              HWND_BOTTOM,         // Placement
  20349.                              id,                  // ID
  20350.                              NULL,                // Ctrl data
  20351.                              NULL) ;              // Pres params
  20352.  
  20353.  The window handles are saved in the hwndButton array. The two IDs are set to
  20354.  0 and 1, as are the indexes to this array. The szButtonLabel array contains
  20355.  the two text strings that appear inside the buttons.
  20356.  
  20357.  The height of the buttons is set to 2 times the height of a character, which
  20358.  is a standard height for push buttons. The width of a push button should be
  20359.  at least the length of the text string inside the button plus two additional
  20360.  character widths. Twelve times the width of a character is adequate for the
  20361.  two buttons in this program.
  20362.  
  20363.  The position parameters of WinCreateWindow are set to 0. Because the buttons
  20364.  will be positioned in the center of the client window, the position can be
  20365.  determined only when the program knows the size of the client win dow. This
  20366.  requires that ClientWndProc call WinSetWindowPos during the WM_SIZE message:
  20367.  
  20368.    for (id = 0 ; id < 2 ; id++)
  20369.         WinSetWindowPos (hwndButton [id], NULL,
  20370.                   cxClient / 2 + (14 * id - 13) * cxChar,
  20371.                   (cyClient - 2 * cyChar) / 2,
  20372.                   0, 0, SWP_MOVE) ;
  20373.  
  20374.  The third and fourth parameters give the position of the control relative to
  20375.  the lower-left corner of the client window. These messy-looking formulas
  20376.  place the buttons side by side in the center of the client window. (Such
  20377.  formulas disappear when you work with controls in dialog boxes.)
  20378.  
  20379.  Push buttons send WM_COMMAND messages to their owners when they are clicked
  20380.  on. The following mp1 and mp2 parameters accompany the WM_COMMAND message:
  20381.  
  20382.     WM_COMMAND Parameter       Meaning
  20383.     SHORT1FROMMP (mp1)         Child ID
  20384.     SHORT1FROMMP (mp2)         CMDSRC_PUSHBUTTON
  20385.     SHORT2FROMMP (mp2)         Nonzero for mouse input; 0 for keyboard input
  20386.  
  20387.  The CMDSRC_PUSHBUTTON identifier indicates that the WM_COMMAND message is
  20388.  sent by a push button. (As you'll see in Chapter 13, menus and keyboard
  20389.  accelerators also send WM_COMMAND messages to the client window. In these
  20390.  cases the low USHORT of mp2 is either CMDSRC_MENU or CMDSRC_ACCELERATOR.)
  20391.  The only way to identify the push button sending the message is to examine
  20392.  the child ID in the low USHORT of mp1, which is why it's so important to
  20393.  give each push button a unique ID.
  20394.  
  20395.  PMWIN.H contains a COMMANDMSG macro that you can use like the CHARMSG and
  20396.  MOUSEMSG macros. The following expression returns the child window ID:
  20397.  
  20398.    COMMANDMSG (&msg) -> cmd
  20399.  
  20400.  The following expression identifies the source of the message:
  20401.  
  20402.    COMMANDMSG (&msg) -> source
  20403.  
  20404.  The following expression is TRUE if the mouse was used:
  20405.  
  20406.    COMMANDMSG (&msg) -> fMouse
  20407.  
  20408.  In the BUTTONS1 program, the push button on the left (containing the text
  20409.  "Smaller") has an ID of 0. The push button with the text "Larger" has an ID
  20410.  of 1. The processing of the WM_COMMAND message in BUTTONS1.C is structured
  20411.  like this:
  20412.  
  20413.    case WM_COMMAND:
  20414.         [other program lines]
  20415.         switch (COMMANDMSG (&msg) -> cmd)
  20416.              {
  20417.              case 0:
  20418.                   [process message from "Smaller" push button]
  20419.                   break ;
  20420.  
  20421.              case 1:
  20422.                   [process message from "Larger" push button]
  20423.                   break ;
  20424.              }
  20425.         [other program lines]
  20426.         return 0 ;
  20427.  
  20428.  When ClientWndProc receives a WM_COMMAND message, it must alter the size of
  20429.  the program's window. The program first obtains the client window's
  20430.  rectangle from WinQueryWindowRect and then translates the coordinates to
  20431.  window coordinates using WinMapWindowPoints. Depending on the ID of the push
  20432.  button that sent the message, BUTTONS1 adjusts the four fields of the
  20433.  rectangle to increase or decrease the size. It then determines the frame
  20434.  rectangle that corresponds to this client rectangle by calling
  20435.  WinCalcFrameRect. BUTTONS1 then sets the new size and position of the frame
  20436.  rectangle by calling WinSetWindowPos.
  20437.  
  20438.  When BUTTONS1 calls WinSetWindowPos, the client window procedure receives a
  20439.  WM_SIZE message. As I've mentioned, BUTTONS1 responds to this by calling
  20440.  WinSetWindowPos to set the new position of the push button controls. Because
  20441.  the frame window is resized equally in all four directions and the push
  20442.  button controls are always positioned in the center of the window, the push
  20443.  buttons remain in the same position relative to the screen.
  20444.  
  20445.  Controls and Keyboard Input Focus
  20446.  
  20447.  When you click on one of the push buttons in BUTTONS1, the push button
  20448.  obtains the input focus, as indicated by a dotted line around the text of
  20449.  the button. Whenever a push button has the input focus, you can also press
  20450.  the Spacebar to trigger the button. However, this is the only keystroke that
  20451.  the push button responds to in a meaningful way. When a dialog box contains
  20452.  push buttons and other controls, you can move the input focus between
  20453.  controls by using the Tab key and, sometimes, the cursor movement keys. The
  20454.  dialog box logic in the Presentation Manager adds this additional keyboard
  20455.  interface──it isn't part of the keyboard logic in individual control
  20456.  windows. In the COLORSCR program shown later in this chapter, we'll examine
  20457.  a way to add a keyboard interface to move the input focus between control
  20458.  windows.
  20459.  
  20460.  Radio Buttons to Indicate Choices
  20461.  
  20462.  The BUTTONS1 program created two push button control windows. Now let's go a
  20463.  little further and write a program that has a few more controls. The
  20464.  DRAWLINE program, shown in Figure 11-3, creates 26 control windows──24
  20465.  radio buttons and two group boxes.
  20466.  
  20467.  Figure 11-3.  The DRAWLINE program.
  20468.  
  20469.    The DRAWLINE File
  20470.  
  20471.    #--------------------
  20472.    # DRAWLINE make file
  20473.    #--------------------
  20474.  
  20475.    drawline.obj : drawline.c
  20476.         cl -c -G2sw -W3 drawline.c
  20477.  
  20478.    drawline.exe : drawline.obj drawline.def
  20479.         link drawline, /align:16, NUL, os2, drawline
  20480.  
  20481.    The DRAWLINE.C File
  20482.  
  20483.    /*--------------------------------------------
  20484.       DRAWLINE.C -- Draw line from radio buttons
  20485.      --------------------------------------------*/
  20486.  
  20487.    #define INCL_WIN
  20488.    #define INCL_GPI
  20489.    #include <os2.h>
  20490.  
  20491.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  20492.  
  20493.    int main (void)
  20494.         {
  20495.         static CHAR  szClientClass[] = "DrawLine" ;
  20496.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  20497.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  20498.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  20499.         HAB          hab ;
  20500.         HMQ          hmq ;
  20501.         HWND         hwndFrame, hwndClient ;
  20502.         QMSG         qmsg ;
  20503.  
  20504.         hab = WinInitialize (0) ;
  20505.         hmq = WinCreateMsgQueue (hab, 0) ;
  20506.  
  20507.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  20508.  
  20509.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  20510.                                         &flFrameFlags, szClientClass, NULL,
  20511.                                         0L, NULL, 0, &hwndClient) ;
  20512.  
  20513.         WinSendMsg (hwndFrame, WM_SETICON,
  20514.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  20515.                     NULL) ;
  20516.  
  20517.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  20518.              WinDispatchMsg (hab, &qmsg) ;
  20519.  
  20520.         WinDestroyWindow (hwndFrame) ;
  20521.         WinDestroyMsgQueue (hmq) ;
  20522.         WinTerminate (hab) ;
  20523.         return 0 ;
  20524.         }
  20525.  
  20526.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  20527.         {
  20528.         static CHAR   *szGroupText[] = { "Color", "Type" } ;
  20529.         static CHAR   *szColorText[] = { "Background", "Blue",      "Red",
  20530.                                           "Pink",       "Green",     "Cyan",
  20531.                                           "Yellow",     "Neutral",   "Dark Gra
  20532.                                           "Dark Blue",  "Dark Red",  "Dark Pin
  20533.                                           "Dark Green", "Dark Cyan", "Brown",
  20534.                                           "Pale Gray" } ;
  20535.  
  20536.         static CHAR   *szTypeText [] = { "Dot",       "Short Dash",
  20537.                                           "Dash Dot",  "Double Dot",
  20538.                                           "Long Dash", "Dash Double Dot",
  20539.                                           "Solid",     "Invisible" } ;
  20540.         static HWND   hwndGroup[2], hwndRadioColor[8], hwndRadioType[8] ;
  20541.         static POINTL aptl[5] ;
  20542.         static SHORT  sCurrentColor = 7,   // Neutral
  20543.                       sCurrentType  = 6 ;  // Solid
  20544.         FONTMETRICS   fm ;
  20545.         HPS           hps ;
  20546.         SHORT         s, id, cxChar, cyChar ;
  20547.  
  20548.         switch (msg)
  20549.              {
  20550.              case WM_CREATE :
  20551.                   hps = WinGetPS (hwnd) ;
  20552.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  20553.                   cxChar = (SHORT) fm.lAveCharWidth ;
  20554.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  20555.                   WinReleasePS (hps) ;
  20556.  
  20557.                   for (s = 0 ; s < 2 ; s++)
  20558.  
  20559.                        hwndGroup[s] = WinCreateWindow (
  20560.                                            hwnd,               // Parent
  20561.                                            WC_STATIC,          // Class
  20562.                                            szGroupText[s],     // Text
  20563.                                            WS_VISIBLE |        // Style
  20564.                                                 SS_GROUPBOX,
  20565.                                            (8 + 42 * s) * cxChar,
  20566.                                            4 * cyChar,         // Position
  20567.                                            (26 + 12 * (1 - s)) *
  20568.                                                 cxChar,        // Width
  20569.                                            14 * cyChar,        // Height
  20570.                                            hwnd,               // Owner
  20571.                                            HWND_TOP,           // Placement
  20572.                                            s + 24,             // ID
  20573.                                            NULL,               // Ctrl data
  20574.                                            NULL) ;             // Pres params
  20575.  
  20576.                   for (s = 0 ; s < 16 ; s++)
  20577.  
  20578.                        hwndRadioColor[s] = WinCreateWindow (
  20579.                                            hwnd,               // Parent
  20580.                                            WC_BUTTON,          // Class
  20581.                                            szColorText[s],     // Text
  20582.                                            WS_VISIBLE |        // Style
  20583.                                                 BS_RADIOBUTTON,
  20584.                                            (10 + (s > 7 ? 18 : 0))
  20585.                                                 * cxChar,      // X Position
  20586.                                            (31 - 3 * (s % 8))
  20587.                                                 * cyChar / 2,  // Y Position
  20588.                                            16 * cxChar,        // Width
  20589.                                            3 * cyChar / 2,     // Height
  20590.                                            hwnd,               // Owner
  20591.                                            HWND_BOTTOM,        // Placement
  20592.                                            s,                  // ID
  20593.                                            NULL,               // Ctrl data
  20594.                                            NULL) ;             // Pres params
  20595.  
  20596.                   for (s = 0 ; s < 8 ; s++)
  20597.  
  20598.                        hwndRadioType[s]  = WinCreateWindow (
  20599.                                            hwnd,               // Parent
  20600.                                            WC_BUTTON,          // Class
  20601.                                            szTypeText[s],      // Text
  20602.                                            WS_VISIBLE |        // Style
  20603.                                                 BS_RADIOBUTTON,
  20604.                                            52 * cxChar,        // Position
  20605.                                            (31 - 3 * s) * cyChar / 2,
  20606.                                            22 * cxChar,        // Width
  20607.                                            3 * cyChar / 2,     // Height
  20608.                                            hwnd,               // Owner
  20609.                                            HWND_BOTTOM,        // Placement
  20610.                                            s + 16,             // ID
  20611.                                            NULL,               // Ctrl data
  20612.                                            NULL) ;             // Pres params
  20613.  
  20614.                   WinSendMsg (hwndRadioColor[sCurrentColor],
  20615.                               BM_SETCHECK, MPFROMSHORT (1), NULL) ;
  20616.  
  20617.                   WinSendMsg (hwndRadioType[sCurrentType],
  20618.                               BM_SETCHECK, MPFROMSHORT (1), NULL) ;
  20619.  
  20620.                   aptl[0].x = aptl[3].x = aptl[4].x = 4 * cxChar ;
  20621.                   aptl[1].x = aptl[2].x = 80 * cxChar ;
  20622.  
  20623.                   aptl[0].y = aptl[1].y = aptl[4].y = 2 * cyChar ;
  20624.                   aptl[2].y = aptl[3].y = 20 * cyChar ;
  20625.  
  20626.                   return 0 ;
  20627.  
  20628.              case WM_CONTROL:
  20629.                   id = SHORT1FROMMP (mp1) ;
  20630.  
  20631.                   if (id < 16)             // Color IDs
  20632.                        {
  20633.                        WinSendMsg (hwndRadioColor[sCurrentColor],
  20634.                                    BM_SETCHECK, MPFROMSHORT (0), NULL) ;
  20635.  
  20636.                        sCurrentColor = id ;
  20637.  
  20638.                        WinSendMsg (hwndRadioColor[sCurrentColor],
  20639.                                    BM_SETCHECK, MPFROMSHORT (1), NULL) ;
  20640.                        }
  20641.  
  20642.                   else if (id < 24)        // Line Type IDs
  20643.                        {
  20644.                        WinSendMsg (hwndRadioType[sCurrentType],
  20645.                                    BM_SETCHECK, MPFROMSHORT (0), NULL) ;
  20646.  
  20647.                        sCurrentType = id - 16 ;
  20648.  
  20649.                        WinSendMsg (hwndRadioType[sCurrentType],
  20650.                                    BM_SETCHECK, MPFROMSHORT (1), NULL) ;
  20651.                        }
  20652.                   WinInvalidateRect (hwnd, NULL, TRUE) ;
  20653.                   return 0 ;
  20654.  
  20655.              case WM_PAINT:
  20656.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  20657.                   GpiErase (hps) ;
  20658.  
  20659.                   GpiSetColor (hps, (LONG) sCurrentColor) ;
  20660.                   GpiSetLineType (hps, sCurrentType + LINETYPE_DOT) ;
  20661.                   GpiMove (hps, aptl) ;
  20662.                   GpiPolyLine (hps, 4L, aptl + 1) ;
  20663.  
  20664.                   WinEndPaint (hps) ;
  20665.                   return 0 ;
  20666.              }
  20667.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  20668.         }
  20669.  
  20670.  
  20671.    The DRAWLINE.DEF File
  20672.  
  20673.    ;-------------------------------------
  20674.    ; DRAWLINE.DEF module definition file
  20675.    ;-------------------------------------
  20676.  
  20677.    NAME           DRAWLINE  WINDOWAPI
  20678.  
  20679.    DESCRIPTION    'Draw Line from Radio Buttons (C) Charles Petzold, 1988'
  20680.    PROTMODE
  20681.    HEAPSIZE       1024
  20682.    STACKSIZE      8192
  20683.    EXPORTS        ClientWndProc
  20684.  
  20685.  
  20686.  DRAWLINE displays two groups of mutually exclusive radio buttons. You
  20687.  specify a line color with one group and a line type (dotted, dashed, solid,
  20688.  and so forth) with the other group. DRAWLINE responds by drawing four-line
  20689.  segments (using the GpiPolyLine function) based on the color and line type
  20690.  you choose. Each group of radio buttons is enclosed in a "group box," which
  20691.  is a control window of the WC_STATIC class. A group box looks like a box
  20692.  with some text at the top. The group box doesn't process keyboard and mouse
  20693.  input and doesn't send messages to its owner.
  20694.  
  20695.  As in BUTTONS1, DRAWLINE creates the control windows in ClientWndProc during
  20696.  processing of the WM_CREATE message. The IDs for the first sixteen radio
  20697.  buttons (those that specify the line color) are 0 through 15. The IDs for
  20698.  the eight line-type radio buttons are 16 through 23. The two group boxes
  20699.  have IDs of 24 and 25. DRAWLINE avoids processing the WM_SIZE message by
  20700.  positioning these control windows relative to the lower-left corner of the
  20701.  client window. This allows the position to be specified in the original
  20702.  WinCreateWindow function. When you first execute DRAWLINE, you may have to
  20703.  increase the size of the window to see all the controls. The DRAWLINE window
  20704.  is shown in Figure 11-4.
  20705.  
  20706.  After DRAWLINE creates the sixteen radio buttons, it sends BM_SETCHECK
  20707.  messages to two radio buttons:
  20708.  
  20709.    WinSendMsg (hwndRadioColor [sCurrentColor],
  20710.                BM_SETCHECK, MPFROMSHORT (1), NULL) ;
  20711.  
  20712.    WinSendMsg (hwndRadioType [sCurrentType],
  20713.                BM_SETCHECK, MPFROMSHORT (1), NULL) ;
  20714.  
  20715.  The BM_SETCHECK message tells a radio button to check or uncheck itself,
  20716.  depending on the value of mp1. These two statements cause a check to appear
  20717.  in the default radio button in each group──the buttons labeled "Neutral"
  20718.  and "Solid." The program keeps track of which radio button is checked in
  20719.  each group with the two static variables sCurrentColor and sCurrentType.
  20720.  When the program begins, the two variables are initialized to 7 and 6, which
  20721.  are the values of CLR_NEUTRAL and LINETYPE_SOLID.
  20722.  
  20723.  When a radio button control is clicked, the control window sends its owner a
  20724.  WM_CONTROL message (not the WM_COMMAND message a push button sends its
  20725.  owner). The mp1 and mp2 parameters for radio buttons are:
  20726.  
  20727.     WM_CONTROL Parameter       Meaning
  20728.     SHORT1FROMMP (mp1)         Child ID
  20729.     SHORT2FROMMP (mp1)         Notification code
  20730.     mp2                        Control window handle
  20731.  
  20732.  As in the WM_COMMAND message, the control window identifies itself by the
  20733.  child ID in the low USHORT of mp1. (Although the mp2 parameter also
  20734.  identifies the control because it contains the control's window handle, some
  20735.  controls that send their owners WM_CONTROL messages use mp2 for other
  20736.  purposes.)
  20737.  
  20738.  The high USHORT of mp1 is a notification code. Radio buttons send WM_CONTROL
  20739.  messages to their owners to indicate one of two occurrences, as shown on the
  20740.  next page.
  20741.  
  20742.     Notification Code          Meaning
  20743.     BN_CLICKED                 Clicked with mouse
  20744.     BN_DBLCLICKED              Double-clicked with mouse
  20745.  
  20746.  DRAWLINE ignores the notification code and accepts either a single click or
  20747.  a double click.
  20748.  
  20749.  DRAWLINE processes the WM_CONTROL message by first obtaining the ID number
  20750.  from mp1:
  20751.  
  20752.    case WM_CONTROL:
  20753.         id = SHORT1FROMMP (mp1) ;
  20754.  
  20755.  If the ID number is from 0 to 15, the radio button being clicked is in the
  20756.  first group of buttons──those that specify the line color. DRAWLINE must
  20757.  uncheck the currently checked radio button in the group and then check the
  20758.  radio button that has sent it the WM_CONTROL message:
  20759.  
  20760.    if (id < 16)             // Color IDs
  20761.         {
  20762.         WinSendMsg (hwndRadioColor [sCurrentColor],
  20763.                     BM_SETCHECK, MPFROMSHORT (0), NULL) ;
  20764.  
  20765.         sCurrentColor = id ;
  20766.  
  20767.         WinSendMsg (hwndRadioColor [sCurrentColor],
  20768.                     BM_SETCHECK, MPFROMSHORT (1), NULL) ;
  20769.         }
  20770.  
  20771.  Notice that the ID number is used as an array index and as the value stored
  20772.  in sCurrentColor. If the ID is from 16 to 23, the radio button is in the
  20773.  second group (line type):
  20774.  
  20775.    else if (id < 24)        // Line type IDs
  20776.         {
  20777.         WinSendMsg (hwndRadioType [sCurrentType],
  20778.                     BM_SETCHECK, MPFROMSHORT (0), NULL) ;
  20779.  
  20780.         sCurrentType = id - 16 ;
  20781.  
  20782.         WinSendMsg (hwndRadioType [sCurrentType],
  20783.                     BM_SETCHECK, MPFROMSHORT (1), NULL) ;
  20784.         }
  20785.  
  20786.  Here the ID must be adjusted by subtracting 16 before it's used as an array
  20787.  index and saved in sCurrentType. In either case, the client window is
  20788.  invalidated to generate a WM_PAINT message:
  20789.  
  20790.    WinInvalidateRect (hwnd, NULL, TRUE) ;
  20791.  
  20792.  During processing of WM_PAINT, DRAWLINE uses the sCurrentColor and
  20793.  sCurrentType variables to set the color and line type:
  20794.  
  20795.    GpiSetColor (hps, (LONG) sCurrentColor) ;
  20796.    GpiSetLineType (hps, sCurrentType + LINETYPE_DOT) ;
  20797.  
  20798.  It then draws the line in the specified color and type.
  20799.  
  20800.  
  20801.  The Scroll-Bar Class
  20802.  
  20803.  A program uses scroll bars to allow a user to specify a single value from a
  20804.  continuous range of integer values (even though the user may not think of
  20805.  the scroll bar in quite this way). As you saw in the series of SYSVALS
  20806.  programs in Chapter 4, you can add a vertical and a horizontal scroll bar
  20807.  to the standard window by including the frame creation flags FCF_VERTSCROLL
  20808.  and FCF_HORZSCROLL in the WinCreateStdWindow function. The vertical scroll
  20809.  bar is always positioned to the right of the client window, and the
  20810.  horizontal scroll bar is always positioned below the client window. (They
  20811.  are children of the frame window rather than the client window.) The scroll
  20812.  bars send messages to their owner (the frame window), which then passes the
  20813.  messages to the client window. You can also create vertical or horizontal
  20814.  scroll-bar control windows anywhere on your client window. These scroll bars
  20815.  send messages to their owner, which most often is the client.
  20816.  
  20817.  The COLORSCR program, shown in Figure 11-5, shows how this is done. This
  20818.  program creates three vertical scroll bars──labeled "Red," "Green," and
  20819.  "Blue"──in the left half of its client window. Each has a range from 0 to
  20820.  255. As you move the slider on each scroll bar, the right half of the client
  20821.  window uses the WinFillRect function to color itself with the composite
  20822.  color based on the red, green, and blue values.
  20823.  
  20824.  Figure 11-5.  The COLORSCR program.
  20825.  
  20826.    The COLORSCR File
  20827.  
  20828.    #--------------------
  20829.    # COLORSCR make file
  20830.    #--------------------
  20831.  
  20832.    colorscr.obj : colorscr.c
  20833.         cl -c -G2sw -W3 colorscr.c
  20834.  
  20835.    colorscr.exe : colorscr.obj colorscr.def
  20836.         link colorscr, /align:16, NUL, os2, colorscr
  20837.  
  20838.    The COLORSCR.C File
  20839.  
  20840.    /*--------------------------------------------------------
  20841.       COLORSCR.C -- Color Scroll using child window controls
  20842.      --------------------------------------------------------*/
  20843.  
  20844.    #define INCL_WIN
  20845.    #define INCL_GPI
  20846.    #include <os2.h>
  20847.    #include <stdlib.h>
  20848.  
  20849.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  20850.    MRESULT EXPENTRY ScrollProc (HWND, USHORT, MPARAM, MPARAM) ;
  20851.  
  20852.    HWND  hwndScroll[3], hwndFocus ;
  20853.    PFNWP pfnOldScroll[3] ;
  20854.  
  20855.    int main (void)
  20856.         {
  20857.         static CHAR  szClientClass[] = "ColorScr" ;
  20858.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  20859.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  20860.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  20861.         HAB          hab ;
  20862.         HMQ          hmq ;
  20863.         HWND         hwndFrame, hwndClient ;
  20864.         QMSG         qmsg ;
  20865.  
  20866.         hab = WinInitialize (0) ;
  20867.         hmq = WinCreateMsgQueue (hab, 0) ;
  20868.  
  20869.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  20870.  
  20871.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  20872.                                         &flFrameFlags, szClientClass, NULL,
  20873.                                         0L, NULL, 0, &hwndClient) ;
  20874.  
  20875.         WinSetFocus (HWND_DESKTOP, hwndFocus = hwndScroll[0]) ;
  20876.  
  20877.         WinSendMsg (hwndFrame, WM_SETICON,
  20878.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  20879.                     NULL) ;
  20880.  
  20881.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  20882.              WinDispatchMsg (hab, &qmsg) ;
  20883.  
  20884.         WinDestroyWindow (hwndFrame) ;
  20885.         WinDestroyMsgQueue (hmq) ;
  20886.         WinTerminate (hab) ;
  20887.         return 0 ;
  20888.         }
  20889.  
  20890.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  20891.         {
  20892.         static CHAR  *szColorLabel[] = { "Red", "Green", "Blue" } ;
  20893.         static HWND  hwndLabel[3], hwndValue[3] ;
  20894.         static SHORT cyChar, sColor[3] ;
  20895.         static RECTL rclRightHalf ;
  20896.         CHAR         szBuffer[10] ;
  20897.         FONTMETRICS  fm ;
  20898.         HPS          hps ;
  20899.         SHORT        s, id, cxClient, cyClient ;
  20900.  
  20901.         switch (msg)
  20902.              {
  20903.              case WM_CREATE :
  20904.                   hps = WinGetPS (hwnd) ;
  20905.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  20906.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  20907.                   WinReleasePS (hps) ;
  20908.  
  20909.                   for (s = 0 ; s < 3 ; s++)
  20910.                        {
  20911.                        hwndScroll[s] = WinCreateWindow (
  20912.                                            hwnd,               // Parent
  20913.                                            WC_SCROLLBAR,       // Class
  20914.  
  20915.                                            NULL,               // Text
  20916.                                            WS_VISIBLE |        // Style
  20917.                                                 SBS_VERT,
  20918.                                            0, 0,               // Position
  20919.                                            0, 0,               // Size
  20920.                                            hwnd,               // Owner
  20921.                                            HWND_BOTTOM,        // Placement
  20922.                                            s,                  // ID
  20923.                                            NULL,               // Ctrl data
  20924.                                            NULL) ;             // Pres params
  20925.  
  20926.                        hwndLabel[s]  = WinCreateWindow (
  20927.                                            hwnd,               // Parent
  20928.                                            WC_STATIC,          // Class
  20929.                                            szColorLabel[s],    // Text
  20930.                                            WS_VISIBLE |        // Style
  20931.                                              SS_TEXT | DT_CENTER,
  20932.                                            0, 0,               // Position
  20933.                                            0, 0,               // Size
  20934.                                            hwnd,               // Owner
  20935.                                            HWND_BOTTOM,        // Placement
  20936.                                            s + 3,              // ID
  20937.                                            NULL,               // Ctrl data
  20938.                                            NULL) ;             // Pres params
  20939.  
  20940.                        hwndValue[s]  = WinCreateWindow (
  20941.                                            hwnd,               // Parent
  20942.                                            WC_STATIC,          // Class
  20943.                                            "0",                // Text
  20944.                                            WS_VISIBLE |        // Style
  20945.                                              SS_TEXT | DT_CENTER,
  20946.                                            0, 0,               // Position
  20947.                                            0, 0,               // Size
  20948.                                            hwnd,               // Owner
  20949.                                            HWND_BOTTOM,        // Placement
  20950.                                            s + 6,              // ID
  20951.                                            NULL,               // Ctrl data
  20952.                                            NULL) ;             // Pres params
  20953.  
  20954.                        pfnOldScroll[s] =
  20955.                                  WinSubclassWindow (hwndScroll[s], ScrollProc)
  20956.  
  20957.                        WinSendMsg (hwndScroll[s], SBM_SETSCROLLBAR,
  20958.                                    MPFROM2SHORT (0, 0), MPFROM2SHORT (0, 255))
  20959.                        }
  20960.                   return 0 ;
  20961.  
  20962.              case WM_SIZE :
  20963.                   cxClient = SHORT1FROMMP (mp2) ;
  20964.                   cyClient = SHORT2FROMMP (mp2) ;
  20965.  
  20966.                   for (s = 0 ; s < 3 ; s++)
  20967.                        {
  20968.                        WinSetWindowPos (hwndScroll[s], NULL,
  20969.                                         (2 * s + 1) * cxClient / 14, 2 * cyCha
  20970.                                         cxClient / 14, cyClient - 4 * cyChar,
  20971.                                         SWP_SIZE | SWP_MOVE) ;
  20972.  
  20973.                        WinSetWindowPos (hwndLabel[s], NULL,
  20974.                                         (4 * s + 1) * cxClient / 28,
  20975.                                         cyClient - 3 * cyChar / 2,
  20976.                                         cxClient / 7, cyChar,
  20977.                                         SWP_SIZE | SWP_MOVE) ;
  20978.  
  20979.                        WinSetWindowPos (hwndValue[s], NULL,
  20980.                                         (4 * s + 1) * cxClient / 28, cyChar /
  20981.                                         cxClient / 7, cyChar,
  20982.                                         SWP_SIZE | SWP_MOVE) ;
  20983.                        }
  20984.  
  20985.                   WinQueryWindowRect (hwnd, &rclRightHalf) ;
  20986.                   rclRightHalf.xLeft = rclRightHalf.xRight / 2 ;
  20987.                   return 0 ;
  20988.  
  20989.              case WM_VSCROLL :
  20990.                   id = SHORT1FROMMP (mp1) ;          // ID of scroll bar
  20991.  
  20992.                   switch (SHORT2FROMMP (mp2))
  20993.                        {
  20994.                        case SB_LINEDOWN :
  20995.                             sColor[id] = min (255, sColor[id] + 1) ;
  20996.                             break ;
  20997.  
  20998.                        case SB_LINEUP :
  20999.                             sColor[id] = max (0, sColor[id] - 1) ;
  21000.                             break ;
  21001.  
  21002.                        case SB_PAGEDOWN :
  21003.                             sColor[id] = min (255, sColor[id] + 16) ;
  21004.                             break ;
  21005.  
  21006.                        case SB_PAGEUP :
  21007.                             sColor[id] = max (0, sColor[id] - 16) ;
  21008.                             break ;
  21009.  
  21010.                        case SB_SLIDERTRACK :
  21011.                             sColor[id] = SHORT1FROMMP (mp2) ;
  21012.                             break ;
  21013.  
  21014.                        default :
  21015.                             return 0 ;
  21016.                        }
  21017.                   WinSendMsg (hwndScroll[id], SBM_SETPOS,
  21018.                               MPFROM2SHORT (sColor[id], 0), NULL) ;
  21019.  
  21020.                   WinSetWindowText (hwndValue[id],
  21021.                                     itoa (sColor[id], szBuffer, 10)) ;
  21022.                   WinInvalidateRect (hwnd, &rclRightHalf, FALSE) ;
  21023.                   return 0 ;
  21024.  
  21025.              case WM_PAINT:
  21026.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  21027.  
  21028.                   GpiCreateLogColorTable (hps, LCOL_RESET, LCOLF_RGB,
  21029.                                                0L, 0L, NULL) ;
  21030.  
  21031.                   WinFillRect (hps, &rclRightHalf, (ULONG) sColor[0] << 16 |
  21032.                                                    (ULONG) sColor[1] <<  8 |
  21033.                                                    (ULONG) sColor[2]) ;
  21034.                   WinEndPaint (hps) ;
  21035.                   return 0 ;
  21036.  
  21037.              case WM_ERASEBACKGROUND:
  21038.                   return 1 ;
  21039.              }
  21040.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  21041.         }
  21042.  
  21043.    MRESULT EXPENTRY ScrollProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
  21044.         {
  21045.  
  21046.         USHORT id ;
  21047.  
  21048.         id = WinQueryWindowUShort (hwnd, QWS_ID) ;   // ID of scroll bar
  21049.  
  21050.         switch (msg)
  21051.              {
  21052.              case WM_CHAR:
  21053.                   if (!(CHARMSG(&msg)->fs & KC_VIRTUALKEY))
  21054.                        break ;
  21055.  
  21056.                   switch (CHARMSG(&msg)->vkey)
  21057.                        {
  21058.                        case VK_TAB:
  21059.                             if (!(CHARMSG(&msg)->fs & KC_KEYUP))
  21060.                                  {
  21061.                                  hwndFocus = hwndScroll[(id + 1) % 3] ;
  21062.                                  WinSetFocus (HWND_DESKTOP, hwndFocus) ;
  21063.                                  }
  21064.                             return 1 ;
  21065.  
  21066.                        case VK_BACKTAB:
  21067.                             if (!(CHARMSG(&msg)->fs & KC_KEYUP))
  21068.                                  {
  21069.                                  hwndFocus = hwndScroll[(id + 2) % 3] ;
  21070.                                  WinSetFocus (HWND_DESKTOP, hwndFocus) ;
  21071.                                  }
  21072.                             return 1 ;
  21073.  
  21074.                        default:
  21075.                             break ;
  21076.                        }
  21077.                   break ;
  21078.  
  21079.              case WM_BUTTON1DOWN:
  21080.                   WinSetFocus (HWND_DESKTOP, hwndFocus = hwnd) ;
  21081.                   break ;
  21082.              }
  21083.         return pfnOldScroll[id] (hwnd, msg, mp1, mp2) ;
  21084.         }
  21085.  
  21086.    The COLORSCR.DEF File
  21087.  
  21088.    ;-------------------------------------
  21089.    ; COLORSCR.DEF module definition file
  21090.    ;-------------------------------------
  21091.  
  21092.    NAME           COLORSCR  WINDOWAPI
  21093.  
  21094.    DESCRIPTION    'Color Scroll (C) Charles Petzold, 1988'
  21095.    PROTMODE
  21096.    HEAPSIZE       1024
  21097.    STACKSIZE      8192
  21098.    EXPORTS        ClientWndProc
  21099.                   ScrollProc
  21100.  
  21101.  
  21102.  COLORSCR creates nine control windows──three scroll-bar windows and six
  21103.  static text windows. Three static text windows are positioned on top of the
  21104.  scroll bars and display the labels "Red," "Green," and "Blue." The text
  21105.  windows on the bottom of each scroll bar display the current position (0
  21106.  through 255) of the scroll bar. These values correspond directly to the red,
  21107.  green, and blue values used to create the composite color. The COLORSCR
  21108.  window is shown in Figure 11-6.
  21109.  
  21110.  The nine windows are created during processing of the WM_CREATE message in
  21111.  ClientWndProc. The child IDs and the arrays the program uses for storing the
  21112.  window handles are as follows:
  21113.  
  21114.     Window Type             Child ID                Window Handle Array
  21115.     Scroll bar              0 to 2                  hwndScroll
  21116.     Static text             3 to 5                  hwndLabel
  21117.     Static text             6 to 8                  hwndValue
  21118.  
  21119.  The three scroll bars have the window class WC_SCROLLBAR and the window
  21120.  style WS_VISIBLE | SBS_VERT. The SBS_VERT style indicates that the scroll
  21121.  bars are vertical. COLORSCR gives the six static text windows the window
  21122.  style WS_VISIBLE | SS_TEXT | DT_CENTER. The SS_TEXT identifier is one of
  21123.  several window styles available for windows of the WC_STATIC class. The
  21124.  DT_CENTER identifier is normally used with the WinDrawText function, but you
  21125.  can also use it as a window style with SS_TEXT to center the text within the
  21126.  width of the window. The position and size parameters are set to 0 in the
  21127.  WinCreateWindow call. ClientWndProc positions and sizes the windows during
  21128.  the WM_SIZE message.
  21129.  
  21130.  When you include scroll bars as part of the standard window, vertical scroll
  21131.  bars always have a standard width, and horizontal scroll bars always have a
  21132.  standard height. You can obtain these standard widths and heights from the
  21133.  WinQuerySysValue function using the SV_CXVSCROLL and SV_CYHSCROLL
  21134.  parameters. However, when you create scroll bars using the WinCreateWindow
  21135.  function, the scroll bars can be any size you want. You can make long, thin
  21136.  scroll bars or short, pudgy scroll bars. COLORSCR always sets the width of
  21137.  the three vertical scroll bars at 1/14 the width of the client window. This
  21138.  is done while processing the WM_SIZE message. If you want to use standard
  21139.  widths and heights for the scroll bars you create in your programs, get the
  21140.  values from WinQuerySysValue.
  21141.  
  21142.  After creating the scroll bars during the WM_CREATE message, COLORSCR sends
  21143.  them a SBM_SETSCROLLBAR message to set the range and current position:
  21144.  
  21145.    WinSendMsg (hwndScroll [s], SBM_SETSCROLLBAR,
  21146.                MPFROM2SHORT (0, 0), MPFROM2SHORT (0, 255)) ;
  21147.  
  21148.  The SYSVALS programs in Chapter 4 use this same message.
  21149.  
  21150.  Processing the Scroll-Bar Messages
  21151.  
  21152.  The scroll bars in COLORSCR send WM_VSCROLL messages to the client window.
  21153.  WM_VSCROLL and WM_HSCROLL messages are accompanied by mp1 and mp2 parameters
  21154.  as follows:
  21155.  
  21156.     WM_xSCROLL Parameter       Meaning
  21157.     SHORT1FROMMP (mp1)         Child ID
  21158.     SHORT2FROMMP (mp1)         0
  21159.     SHORT1FROMMP (mp2)         Slider position (for some commands)
  21160.     SHORT2FROMMP (mp2)         Command
  21161.  
  21162.  This is the same information used for scroll bars created in a standard
  21163.  window. The SYSVALS program didn't look at the control ID, because the
  21164.  program had only one vertical scroll bar and one horizontal scroll bar. If
  21165.  you include scroll bars as part of the standard window and then create
  21166.  additional scroll bars using WinCreateWindow, you can differentiate the
  21167.  scroll bars by examining the ID number. The scroll bars that are part of the
  21168.  standard window have the predefined IDs FID_VERTSCROLL and FID_HORZSCROLL.
  21169.  
  21170.  COLORSCR defines a static array named sColor to store the current position
  21171.  of each of the three scroll bars. The index to this array is the same as the
  21172.  scroll-bar ID. The processing of the WM_VSCROLL message thus begins by
  21173.  obtaining the ID number from mp1:
  21174.  
  21175.    case WM_VSCROLL :
  21176.         id = SHORT1FROMMP (mp1) ;
  21177.  
  21178.  COLORSCR then alters the appropriate value stored in sColor based on the
  21179.  scroll-bar command in the high USHORT of mp2:
  21180.  
  21181.    switch (SHORT2FROMMP (mp2))
  21182.         {
  21183.         case SB_LINEDOWN :
  21184.              sColor [id] = min (255, sColor [id] + 1) ;
  21185.              break ;
  21186.  
  21187.         case SB_LINEUP :
  21188.              sColor [id] = max (0, sColor [id] - 1) ;
  21189.              break ;
  21190.  
  21191.  The program sets a new position of the scroll-bar slider by sending it an
  21192.  SBM_SETPOS message. The window handle of the scroll bar is stored in the
  21193.  hwndScroll array that is also indexed by the ID number:
  21194.    WinSendMsg (hwndScroll [id], SBM_SETPOS,
  21195.                MPFROM2SHORT (sColor [id], 0), NULL) ;
  21196.  
  21197.  COLORSCR must also change the text in the static control window displayed at
  21198.  the bottom of the scroll bar. It does this by first converting the number to
  21199.  its ASCII value using the C function itoa and then calling WinSetWindowText.
  21200.  The window handles are stored in the hwndValue array:
  21201.  
  21202.    WinSetWindowText (hwndValue [id],
  21203.                      itoa (sColor [id], szBuffer, 10)) ;
  21204.  
  21205.  The right half of the client window is then invalidated to generate a
  21206.  WM_PAINT message:
  21207.  
  21208.    WinInvalidateRect (hwnd, &rclRightHalf, FALSE) ;
  21209.  
  21210.  The rclRightHalf RECTL structure contains the coordinates of the right half
  21211.  of the client window. These are set during the WM_SIZE message.
  21212.  
  21213.  The WM_PAINT processing is fairly simple. COLORSCR first calls
  21214.  GpiCreateLogColorTable to specify that color indexes are to be interpreted
  21215.  as 32-bit RGB values:
  21216.  
  21217.    GpiCreateLogColorTable (hps, LCOL_RESET, LCOLF_RGB,
  21218.                                 0L, 0L, NULL) ;
  21219.  
  21220.  The program then combines the current red, green, and blue color values
  21221.  stored in the sColor array into one ULONG and calls WinFillRect to color the
  21222.  right half of the client window with that color:
  21223.  
  21224.    WinFillRect (hps, &rclRightHalf, (ULONG) sColor [0] << 16 |
  21225.                                     (ULONG) sColor [1] <<  8 |
  21226.                                     (ULONG) sColor [2]) ;
  21227.  
  21228.  Changing the Keyboard Input Focus
  21229.  
  21230.  Unlike BUTTONS1 and DRAWLINE, COLORSCR has a complete keyboard interface and
  21231.  doesn't require a mouse. You can move the position of the scroll-bar slider
  21232.  using the cursor movement arrow keys, and you can move the keyboard input
  21233.  focus from one scroll bar to another using the Tab and Shift-Tab keys.
  21234.  
  21235.  As you saw in the SYSVALS program in Chapter 4, the scroll bars include
  21236.  their own keyboard interface for the cursor movement keys. Once a scroll-
  21237.  bar window has the keyboard input focus, it can understand and interpret
  21238.  these keys. That's not the problem. The problem is that once a control
  21239.  window gets the input focus, it doesn't properly interpret the Tab key. We
  21240.  need to find a way to give a scroll bar the input focus (so that it uses the
  21241.  cursor movement keys) and then be able to take away the input focus when the
  21242.  Tab key is pressed. But how can ClientWndProc know that the Tab key is
  21243.  pressed when the scroll bar is getting all the WM_CHAR messages?
  21244.  
  21245.  The solution involves a technique called "window subclassing." Essentially,
  21246.  this technique allows your program to get first dibs on all messages sent to
  21247.  a particular window created by your program (but not windows created by
  21248.  other programs). You can process some of these messages and then allow the
  21249.  window's normal window procedure to process the others. You can prevent the
  21250.  normal window procedure from receiving some messages, or you can alter
  21251.  messages before they get to the window procedure.
  21252.  
  21253.  Let's look at COLORSCR to see how this works in practice. Toward the end of
  21254.  COLORSCR.C is a function called ScrollProc that is defined as if it were a
  21255.  normal window procedure. It is an EXPENTRY function, has a return value of
  21256.  MRESULT, and accepts the four parameters normally passed to window
  21257.  procedures. ScrollProc is also included in the EXPORTS section of the
  21258.  COLORSCR.DEF module definition file:
  21259.  
  21260.    EXPORTS        ClientWndProc
  21261.                   ScrollProc
  21262.  
  21263.  Don't forget to do this!
  21264.  
  21265.  After creating the three scroll-bar windows during processing of the
  21266.  WM_CREATE message, COLORSCR calls WinSubclassWindow to specify that all
  21267.  messages to these three scroll-bar windows should be sent to ScrollProc
  21268.  instead:
  21269.  
  21270.    pfnOldScroll[s] =
  21271.              WinSubclassWindow (hwndScroll[s], ScrollProc) ;
  21272.  
  21273.  The pfnOldScroll array is a global variable defined near the top of
  21274.  COLORSCR.C:
  21275.  
  21276.    PFNWP pfnOldScroll[3] ;
  21277.  
  21278.  This array holds the addresses of the original window procedures for the
  21279.  three scroll bars.
  21280.  
  21281.  Now let's look at ScrollProc. When a message is sent to any of the three
  21282.  scroll bars, ScrollProc gets the message rather than the normal scroll-bar
  21283.  window procedure. ScrollProc obtains the control ID associated with the
  21284.  scroll-bar window receiving the message:
  21285.  
  21286.    id = WinQueryWindowUShort (hwnd, QWS_ID) ;
  21287.  
  21288.  ScrollProc then checks to see if the message is WM_CHAR, if the message
  21289.  contains a valid virtual key code, if the key is being pressed, and if the
  21290.  virtual key is VK_TAB or VK_BACKTAB.
  21291.  
  21292.  For VK_TAB, ScrollProc determines the window handle that is to receive the
  21293.  input focus:
  21294.  
  21295.    hwndFocus = hwndScroll[(id + 1) % 3]
  21296.  
  21297.  
  21298.  The new focus window is the scroll bar with the next highest ID. ScrollProc
  21299.  then uses this hwndFocus variable to set the new focus window:
  21300.  
  21301.    WinSetFocus (HWND_DESKTOP, hwndFocus) ;
  21302.  
  21303.  ScrollProc also sets the focus to one of the scroll bars when it receives a
  21304.  WM_BUTTON1DOWN message.
  21305.  
  21306.  ScrollProc sends all messages (except the VK_TAB and VK_BACKTAB keystrokes)
  21307.  to the old scroll-bar window procedure stored in pfnOldScroll:
  21308.  
  21309.    return pfnOldScroll [id] (hwnd, msg, mp1, mp2) ;
  21310.  
  21311.  This allows the normal processing in the scroll-bar window procedure to
  21312.  occur.
  21313.  
  21314.  Of course, we must make sure that the first scroll bar gets the input focus
  21315.  when the program starts up. Following the WinCreateStdWindow call in main,
  21316.  COLORSCR sets the input focus to the first window:
  21317.  
  21318.    WinSetFocus (HWND_DESKTOP, hwndFocus = hwndScroll [0]) ;
  21319.  
  21320.  Without this statement, the first scroll bar wouldn't get the input focus
  21321.  until it was clicked.
  21322.  
  21323.  
  21324.  Creating Your Own Controls
  21325.  
  21326.  In Chapter 9's CHECKER3 program you created child windows that helped
  21327.  simplify mouse processing. These child windows were not really control
  21328.  windows because they had two deficiencies: They had no keyboard interface
  21329.  and they did not notify their owner when they were toggled. Now let's try
  21330.  something similar, but this time let's make the children full-fledged
  21331.  control windows.
  21332.  
  21333.  In this exercise, we're going to reinvent the push button. While we're at
  21334.  it, we're going to make our push buttons look a little prettier than the
  21335.  ones built into the Presentation Manager. This new push button will be
  21336.  square and (through use of color) will have a 3-D appearance.
  21337.  
  21338.  Mouse Capture and Input Focus
  21339.  
  21340.  You may want to experiment with BUTTONS1 (and other programs that create
  21341.  control windows based on the predefined window classes) to help you
  21342.  understand what is going on in the window procedure for the control.
  21343.  
  21344.  For example, you'll find in BUTTONS1 that triggering the button with the
  21345.  mouse requires that you both press and release the mouse button while the
  21346.  pointer is positioned within the control. If you press the mouse button when
  21347.  the pointer is within the control, the control is inverted. If you move the
  21348.  mouse pointer outside the control with the mouse button pressed, the control
  21349.  returns to normal. Moving the pointer back within the button causes the
  21350.  button colors to be inverted again.
  21351.  
  21352.  Obviously the window procedure is capturing the mouse (a concept discussed
  21353.  in Chapter 9). This is the only way the window procedure can detect that
  21354.  the mouse pointer has moved outside the control window.
  21355.  
  21356.  Clicking the push button with the mouse causes a dotted outline to appear
  21357.  around the text. This indicates that the control has the input focus and
  21358.  requires that the window procedure give itself the input focus when the
  21359.  button is clicked. When the push button has the input focus, you can also
  21360.  trigger the button by pressing the Spacebar. This requires that the window
  21361.  procedure for the control also process some keystrokes.
  21362.  
  21363.  The Square Button Window Procedure
  21364.  
  21365.  Let's look first at the SQBTN.C file shown in Figure 11-7.
  21366.  
  21367.  Figure 11-7.  The SQBTN.C file.
  21368.  
  21369.    /*----------------------------------------------------------------
  21370.       SQBTN.C -- Contains window procedure for square 3D push button
  21371.      ----------------------------------------------------------------*/
  21372.  
  21373.    #define INCL_WIN
  21374.    #define INCL_GPI
  21375.    #include <os2.h>
  21376.    #include <malloc.h>
  21377.    #include <string.h>
  21378.  
  21379.    #define LCID_ITALIC 1L
  21380.  
  21381.                   /*--------------------------------------------------
  21382.                      Structure for storing data unique to each window
  21383.                     --------------------------------------------------*/
  21384.    typedef struct
  21385.         {
  21386.         PSZ  pszText ;
  21387.         BOOL fHaveCapture ;
  21388.         BOOL fHaveFocus ;
  21389.         BOOL fInsideRect ;
  21390.         BOOL fSpaceDown ;
  21391.         }
  21392.         SQBTN ;
  21393.  
  21394.    typedef SQBTN FAR *PSQBTN ;
  21395.    MRESULT EXPENTRY SqBtnWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  21396.    VOID             DrawButton   (HWND, HPS, PSQBTN) ;
  21397.  
  21398.    HAB  hab ;
  21399.  
  21400.              /*--------------------------------------------------------
  21401.                 RegisterSqBtnClass function available to other modules
  21402.                --------------------------------------------------------*/
  21403.  
  21404.    BOOL RegisterSqBtnClass (HAB habIn)
  21405.         {
  21406.         hab = habIn ;
  21407.  
  21408.         return WinRegisterClass (hab, "SqBtn", SqBtnWndProc,
  21409.                                  CS_SIZEREDRAW, sizeof (PSQBTN)) ;
  21410.         }
  21411.  
  21412.              /*-------------------------------------------
  21413.                 String functions that accept far pointers
  21414.                -------------------------------------------*/
  21415.  
  21416.    USHORT fstrlen (PCHAR pch)
  21417.         {
  21418.         USHORT usLen ;
  21419.         for (usLen = 0 ; pch[usLen] ; usLen++) ;
  21420.         return usLen ;
  21421.         }
  21422.  
  21423.    PCHAR fstrcpy (PCHAR pchDst, PCHAR pchSrc)
  21424.         {
  21425.         USHORT usIndex ;
  21426.         for (usIndex = 0 ; pchDst[usIndex] = pchSrc[usIndex] ; usIndex++) ;
  21427.         return pchDst ;
  21428.         }
  21429.  
  21430.              /*-------------------------------
  21431.                 SqBtnWndProc window procedure
  21432.                -------------------------------*/
  21433.  
  21434.    MRESULT EXPENTRY SqBtnWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp
  21435.         {
  21436.         BOOL          fTestInsideRect ;
  21437.         HPS           hps ;
  21438.         PCREATESTRUCT pcrst ;
  21439.         POINTL        ptl ;
  21440.         PSQBTN        pSqBtn ;
  21441.         PWNDPARAMS    pwprm ;
  21442.         RECTL         rcl ;
  21443.  
  21444.         pSqBtn = WinQueryWindowPtr (hwnd, 0) ;
  21445.  
  21446.         switch (msg)
  21447.              {
  21448.              case WM_CREATE:
  21449.                   pSqBtn = _fmalloc (sizeof (SQBTN)) ;
  21450.  
  21451.                             // Initialize structure
  21452.  
  21453.                   pSqBtn->fHaveCapture = FALSE ;
  21454.                   pSqBtn->fHaveFocus   = FALSE ;
  21455.                   pSqBtn->fInsideRect  = FALSE ;
  21456.                   pSqBtn->fSpaceDown   = FALSE ;
  21457.  
  21458.                             // Get window text from creation structure
  21459.  
  21460.                   pcrst = (PCREATESTRUCT) PVOIDFROMMP (mp2) ;
  21461.  
  21462.                   pSqBtn->pszText = _fmalloc (1 + fstrlen (pcrst->pszText)) ;
  21463.                   fstrcpy (pSqBtn->pszText, pcrst->pszText) ;
  21464.  
  21465.                   WinSetWindowPtr (hwnd, 0, pSqBtn) ;
  21466.                   return 0 ;
  21467.  
  21468.              case WM_SETWINDOWPARAMS:
  21469.                   pwprm = (PWNDPARAMS) PVOIDFROMMP (mp1) ;
  21470.  
  21471.                             // Get window text from window parameter structure
  21472.  
  21473.                   if (pwprm->fsStatus & WPM_TEXT)
  21474.                        {
  21475.                        _ffree (pSqBtn->pszText) ;
  21476.                        pSqBtn->pszText = _fmalloc (1 + pwprm->cchText) ;
  21477.                        fstrcpy (pSqBtn->pszText, pwprm->pszText) ;
  21478.                        }
  21479.                   return 1 ;
  21480.  
  21481.              case WM_QUERYWINDOWPARAMS:
  21482.                   pwprm == (PWNDPARAMS) PVOIDFROMMP (mp1) ;
  21483.  
  21484.                             // Set window parameter structure fields
  21485.  
  21486.                   if (pwprm->fsStatus & WPM_CCHTEXT)
  21487.                        pwprm->cchText = fstrlen (pSqBtn->pszText) ;
  21488.  
  21489.                   if (pwprm->fsStatus & WPM_TEXT)
  21490.                        fstrcpy (pwprm->pszText, pSqBtn->pszText) ;
  21491.  
  21492.                   if (pwprm->fsStatus & WPM_CBPRESPARAMS)
  21493.                        pwprm->cbPresParams = 0 ;
  21494.  
  21495.                   if (pwprm->fsStatus & WPM_PRESPARAMS)
  21496.                        pwprm->pPresParams = NULL ;
  21497.  
  21498.                   if (pwprm->fsStatus & WPM_CBCTLDATA)
  21499.                        pwprm->cbCtlData = 0 ;
  21500.  
  21501.                   if (pwprm->fsStatus & WPM_CTLDATA)
  21502.                        pwprm->pCtlData = NULL ;
  21503.  
  21504.                   return 1 ;
  21505.  
  21506.              case WM_BUTTON1DOWN:
  21507.                   WinSetFocus (HWND_DESKTOP, hwnd) ;
  21508.                   WinSetCapture (HWND_DESKTOP, hwnd) ;
  21509.                   pSqBtn->fHaveCapture = TRUE ;
  21510.                   pSqBtn->fInsideRect  = TRUE ;
  21511.                   WinInvalidateRect (hwnd, NULL, FALSE) ;
  21512.                   return 0 ;
  21513.  
  21514.              case WM_MOUSEMOVE:
  21515.                   if (!pSqBtn->fHaveCapture)
  21516.                        break ;
  21517.  
  21518.                   WinQueryWindowRect (hwnd, &rcl) ;
  21519.                   ptl.x = MOUSEMSG(&msg)->x ;
  21520.                   ptl.y = MOUSEMSG(&msg)->y ;
  21521.                             // Test if mouse pointer is still in window
  21522.  
  21523.                   fTestInsideRect = WinPtInRect (hab, &rcl, &ptl) ;
  21524.  
  21525.                   if (pSqBtn->fInsideRect != fTestInsideRect)
  21526.                        {
  21527.                        pSqBtn->fInsideRect = fTestInsideRect ;
  21528.                        WinInvalidateRect (hwnd, NULL, FALSE) ;
  21529.                        }
  21530.                   break ;
  21531.  
  21532.             case WM_BUTTON1UP:
  21533.                   if (!pSqBtn->fHaveCapture)
  21534.                        break ;
  21535.  
  21536.                   WinSetCapture (HWND_DESKTOP, NULL) ;
  21537.                   pSqBtn->fHaveCapture = FALSE ;
  21538.                   pSqBtn->fInsideRect  = FALSE ;
  21539.  
  21540.                   WinQueryWindowRect (hwnd, &rcl) ;
  21541.                   ptl.x = MOUSEMSG(&msg)->x ;
  21542.                   ptl.y = MOUSEMSG(&msg)->y ;
  21543.  
  21544.                             // Post WM_COMMAND if mouse pointer is in window
  21545.  
  21546.                   if (WinPtInRect (hab, &rcl, &ptl))
  21547.                        WinPostMsg (WinQueryWindow (hwnd, QW_OWNER, FALSE),
  21548.                             WM_COMMAND,
  21549.                             MPFROMSHORT (WinQueryWindowUShort (hwnd, QWS_ID)),
  21550.                             MPFROM2SHORT (CMDSRC_OTHER, TRUE)) ;
  21551.  
  21552.                   WinInvalidateRect (hwnd, NULL, FALSE) ;
  21553.                   return 0 ;
  21554.  
  21555.              case WM_ENABLE:
  21556.                   WinInvalidateRect (hwnd, NULL, FALSE) ;
  21557.                   return 0 ;
  21558.  
  21559.              case WM_SETFOCUS:
  21560.                   pSqBtn->fHaveFocus = SHORT1FROMMP (mp2) ;
  21561.                   WinInvalidateRect (hwnd, NULL, FALSE) ;
  21562.                   return 0 ;
  21563.              case WM_CHAR:
  21564.                   if (!(CHARMSG(&msg)->fs & KC_VIRTUALKEY) ||
  21565.                         CHARMSG(&msg)->vkey != VK_SPACE    ||
  21566.                         CHARMSG(&msg)->fs & KC_PREVDOWN)
  21567.                        break ;
  21568.  
  21569.                             // Post WM_COMMAND when space bar is released
  21570.  
  21571.                   if (!(CHARMSG(&msg)->fs & KC_KEYUP))
  21572.                        pSqBtn->fSpaceDown = TRUE ;
  21573.                   else
  21574.                        {
  21575.                        pSqBtn->fSpaceDown = FALSE ;
  21576.                        WinPostMsg (WinQueryWindow (hwnd, QW_OWNER, FALSE),
  21577.                             WM_COMMAND,
  21578.                             MPFROMSHORT (WinQueryWindowUShort (hwnd, QWS_ID)),
  21579.                             MPFROM2SHORT (CMDSRC_OTHER, FALSE)) ;
  21580.                        }
  21581.                   WinInvalidateRect (hwnd, NULL, FALSE) ;
  21582.                   return 0 ;
  21583.  
  21584.              case WM_PAINT:
  21585.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  21586.                   DrawButton (hwnd, hps, pSqBtn) ;
  21587.                   WinEndPaint (hps) ;
  21588.                   return 0 ;
  21589.  
  21590.              case WM_DESTROY:
  21591.                   _ffree (pSqBtn->pszText) ;
  21592.                   _ffree (pSqBtn) ;
  21593.                   return 0 ;
  21594.              }
  21595.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  21596.         }
  21597.  
  21598.              /*--------------------------------------------------------
  21599.                 Draws filled and outlined polygon (used by DrawButton)
  21600.                --------------------------------------------------------*/
  21601.  
  21602.    VOID Polygon (HPS hps, LONG lPoints, POINTL aptl[], LONG lColor)
  21603.         {
  21604.                   // Draw interior in specified color
  21605.         GpiSavePS (hps) ;
  21606.         GpiSetColor (hps, lColor) ;
  21607.  
  21608.         GpiBeginArea (hps, BA_NOBOUNDARY | BA_ALTERNATE) ;
  21609.         GpiMove (hps, aptl) ;
  21610.         GpiPolyLine (hps, lPoints - 1, aptl + 1) ;
  21611.         GpiEndArea (hps) ;
  21612.  
  21613.         GpiRestorePS (hps, -1L) ;
  21614.  
  21615.                   // Draw boundary in default color
  21616.  
  21617.         GpiMove (hps, aptl + lPoints - 1) ;
  21618.         GpiPolyLine (hps, lPoints, aptl) ;
  21619.         }
  21620.  
  21621.              /*---------------------
  21622.                 Draws square button
  21623.                ---------------------*/
  21624.  
  21625.    VOID DrawButton (HWND hwnd, HPS hps, PSQBTN pSqBtn)
  21626.         {
  21627.         FATTRS      fat ;
  21628.         FONTMETRICS fm ;
  21629.         HDC         hdc ;
  21630.         LONG        lColor, lHorzRes, lVertRes, cxEdge, cyEdge ;
  21631.         POINTL      aptl[10], aptlTextBox[TXTBOX_COUNT], ptlShadow, ptlText ;
  21632.         RECTL       rcl ;
  21633.  
  21634.                   // Find 2 millimeter edge width in pixels
  21635.  
  21636.         hdc = GpiQueryDevice (hps) ;
  21637.         DevQueryCaps (hdc, CAPS_HORIZONTAL_RESOLUTION, 1L, &lHorzRes) ;
  21638.         DevQueryCaps (hdc, CAPS_VERTICAL_RESOLUTION,   1L, &lVertRes) ;
  21639.  
  21640.         cxEdge = lHorzRes / 500 ;
  21641.         cyEdge = lVertRes / 500 ;
  21642.  
  21643.                   // Set up coordinates for drawing the button
  21644.  
  21645.         WinQueryWindowRect (hwnd, &rcl) ;
  21646.  
  21647.         aptl[0].x = 0 ;                    aptl[0].y = 0 ;
  21648.         aptl[1].x = cxEdge ;               aptl[1].y = cyEdge ;
  21649.         aptl[2].x = rcl.xRight - cxEdge ;  aptl[2].y = cyEdge ;
  21650.         aptl[3].x = rcl.xRight - 1 ;       aptl[3].y = 0 ;
  21651.         aptl[4].x = rcl.xRight - 1 ;       aptl[4].y = rcl.yTop - 1 ;
  21652.         aptl[5].x = rcl.xRight - cxEdge ;  aptl[5].y = rcl.yTop - cyEdge ;
  21653.         aptl[6].x = cxEdge ;               aptl[6].y = rcl.yTop - cyEdge ;
  21654.         aptl[7].x = 0 ;                    aptl[7].y = rcl.yTop - 1 ;
  21655.         aptl[8].x = 0 ;                    aptl[8].y = 0 ;
  21656.         aptl[9].x = cxEdge ;               aptl[9].y = cyEdge ;
  21657.  
  21658.                   // Paint edges at bottom and right side
  21659.  
  21660.         GpiSetColor (hps, CLR_BLACK) ;
  21661.         lColor = (pSqBtn->fInsideRect || pSqBtn->fSpaceDown) ?
  21662.                                  CLR_PALEGRAY : CLR_DARKGRAY ;
  21663.         Polygon (hps, 4L, aptl + 0, lColor) ;
  21664.         Polygon (hps, 4L, aptl + 2, lColor) ;
  21665.  
  21666.                   // Paint edges at top and left side
  21667.  
  21668.         lColor = (pSqBtn->fInsideRect || pSqBtn->fSpaceDown) ?
  21669.                                  CLR_DARKGRAY : CLR_WHITE ;
  21670.         Polygon (hps, 4L, aptl + 4, lColor) ;
  21671.         Polygon (hps, 4L, aptl + 6, lColor) ;
  21672.  
  21673.                   // Paint interior area
  21674.  
  21675.         GpiSavePS (hps) ;
  21676.         GpiSetColor (hps, (pSqBtn->fInsideRect || pSqBtn->fSpaceDown) ?
  21677.                                  CLR_DARKGRAY : CLR_PALEGRAY) ;
  21678.         GpiMove (hps, aptl + 1) ;
  21679.         GpiBox (hps, DRO_FILL, aptl + 5, 0L, 0L) ;
  21680.         GpiRestorePS (hps, -1L) ;
  21681.         GpiBox (hps, DRO_OUTLINE, aptl + 5, 0L, 0L) ;
  21682.  
  21683.                   // If button has focus, use italic font
  21684.  
  21685.         GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  21686.  
  21687.         if (pSqBtn->fHaveFocus)
  21688.              {
  21689.              fat.usRecordLength  = sizeof fat ;
  21690.              fat.fsSelection     = FATTR_SEL_ITALIC ;
  21691.              fat.lMatch          = 0 ;
  21692.              fat.idRegistry      = fm.idRegistry ;
  21693.              fat.usCodePage      = fm.usCodePage ;
  21694.              fat.lMaxBaselineExt = fm.lMaxBaselineExt ;
  21695.              fat.lAveCharWidth   = fm.lAveCharWidth ;
  21696.              fat.fsType          = 0 ;
  21697.              fat.fsFontUse       = 0 ;
  21698.              strcpy (fat.szFacename, fm.szFacename) ;
  21699.  
  21700.              GpiCreateLogFont (hps, NULL, LCID_ITALIC, &fat) ;
  21701.              GpiSetCharSet (hps, LCID_ITALIC) ;
  21702.              }
  21703.                   // Calculate text position
  21704.  
  21705.         GpiQueryTextBox (hps, (LONG) fstrlen (pSqBtn->pszText), pSqBtn->pszTex
  21706.                               TXTBOX_COUNT, aptlTextBox) ;
  21707.  
  21708.         ptlText.x = (rcl.xRight - aptlTextBox[TXTBOX_CONCAT].x) / 2 ;
  21709.         ptlText.y = (rcl.yTop   - aptlTextBox[TXTBOX_TOPLEFT].y -
  21710.                                   aptlTextBox[TXTBOX_BOTTOMLEFT].y) / 2 ;
  21711.  
  21712.         ptlShadow.x = ptlText.x + fm.lAveCharWidth   / 3 ;
  21713.         ptlShadow.y = ptlText.y - fm.lMaxBaselineExt / 8 ;
  21714.  
  21715.                   // Display text shadow in black, and text in white
  21716.  
  21717.         GpiSetColor (hps, CLR_BLACK) ;
  21718.         GpiCharStringAt (hps, &ptlShadow, (LONG) fstrlen (pSqBtn->pszText),
  21719.                                           pSqBtn->pszText) ;
  21720.         GpiSetColor (hps, CLR_WHITE) ;
  21721.         GpiCharStringAt (hps, &ptlText, (LONG) fstrlen (pSqBtn->pszText),
  21722.                                         pSqBtn->pszText) ;
  21723.  
  21724.                   // X out button if the window is not enabled
  21725.  
  21726.         if (!WinIsWindowEnabled (hwnd))
  21727.              {
  21728.              GpiMove (hps, aptl + 1) ;
  21729.              GpiLine (hps, aptl + 5) ;
  21730.              GpiMove (hps, aptl + 2) ;
  21731.              GpiLine (hps, aptl + 6) ;
  21732.              }
  21733.                   // Clean up
  21734.  
  21735.         if (pSqBtn->fHaveFocus)
  21736.              {
  21737.              GpiSetCharSet (hps, LCID_DEFAULT) ;
  21738.              GpiDeleteSetId (hps, LCID_ITALIC) ;
  21739.              }
  21740.         }
  21741.  
  21742.  
  21743.  This file contains several functions. Two functions are called from outside
  21744.  the module: RegisterSqBtnClass registers a window class called "SqBtn" that
  21745.  uses the window procedure SqBtnWndProc, another function in SQBTN.C.
  21746.  RegisterSqBtnClass also saves the process's anchor block handle in a global
  21747.  variable for later use in the window procedure.
  21748.  
  21749.  Often a program creates more than one child window based on the same window
  21750.  class. This means that you cannot use static variables to store information
  21751.  unique to each child window: These static variables would be shared by all
  21752.  windows based on that class that are created within the same process. For
  21753.  this reason, only automatic variables (used during the course of processing
  21754.  a single message) are defined within SqBtnWndProc.
  21755.  
  21756.  Information unique to each window is stored in a structure of type SQBTN,
  21757.  defined in the SQBTN.C file. When RegisterSqBtnClass registers the window
  21758.  class, the last parameter of WinRegisterClass is set to the size of a far
  21759.  pointer to the SQBTN structure. This reserves some memory space that is
  21760.  unique to each window. During the WM_CREATE message, SqBtnWndProc calls _
  21761.  fmalloc (a version of malloc that returns a far pointer) to allocate a block
  21762.  of memory the size of the SQBTN structure. The pointer returned by _ fmalloc
  21763.  is stored in the variable pSqBtn. After the fields of this structure are
  21764.  initialized, the pointer is saved in the memory reserved by the
  21765.  WinRegisterClass function:
  21766.  
  21767.    WinSetWindowPtr (hwnd, 0, pSqBtn) ;
  21768.  
  21769.  The WM_CREATE message is the first message the window procedure processes
  21770.  when creating a new window. For all other messages, the pointer stored in
  21771.  the reserved area will be valid. SqBtnWndProc obtains that pointer before
  21772.  processing any specific message:
  21773.  
  21774.    pSqBtn = WinQueryWindowPtr (hwnd, 0) ;
  21775.  
  21776.  This allows the window procedure to use the window-specific information
  21777.  stored in the structure.
  21778.  
  21779.  Some windows have a "window text" that the window displays. For example,
  21780.  push buttons display their window text in the center of the button. Windows
  21781.  that have a window text must save the text themselves. This requires some
  21782.  additional processing in the WM_CREATE message. During WM_CREATE, a pointer
  21783.  to the initial window text of the window (which is the string passed as the
  21784.  window text parameter to WinCreateWindow) is stored in the pszText field of
  21785.  a CREATESTRUCT structure for the window.
  21786.  
  21787.  The mp2 message parameter contains a pointer to this structure. SqBtnWndProc
  21788.  must determine the length of this text, allocate memory for storing the text
  21789.  by calling _ fmalloc, copy the text into this memory, and save the pointer
  21790.  returned from _ fmalloc in the SQBTN structure.
  21791.  
  21792.  Now we have a little problem because we're compiling for small model but the
  21793.  pointer to this text in the CREATESTRUCT structure is a far pointer. This
  21794.  means that we cannot use the normal C strlen and strcpy functions for
  21795.  working with this string. For this reason, the SQBTN.C file has two
  21796.  functions named fstrlen and fstrcpy, which are equivalent to strlen and
  21797.  strcpy but which use far pointers. (Another way around this problem is to
  21798.  compile for medium or large model.)
  21799.  
  21800.  The window text can be changed by a call to WinSetWindowText and queried by
  21801.  a call to WinQueryWindowText. These functions send WM_SETWINDOWPARAMS and
  21802.  WM_QUERYWINDOWPARAMS messages, respectively, to the window procedure. This
  21803.  requires that SqBtnWndProc also process these two messages. Again the
  21804.  fstrlen and fstrcpy functions are used in working with the text string.
  21805.  
  21806.  During the WM_BUTTON1DOWN message, SqBtnWndProc captures the mouse and sets
  21807.  the fHaveCapture field in the SQBTN structure to TRUE. The window procedure
  21808.  tests this field during the WM_MOUSEMOVE and WM_BUTTON1UP message to
  21809.  determine if it can ignore the message. For both of these messages,
  21810.  SqBtnWndProc uses the WinPtInRect to determine if the mouse pointer is still
  21811.  within the area occupied by the control window. If the mouse pointer is
  21812.  within the window during a WM_BUTTON1UP message, SqBtnWndProc posts a
  21813.  WM_COMMAND message to its owner.
  21814.  
  21815.  The WM_COMMAND message can also be posted during the WM_CHAR message. The
  21816.  window procedure posts this message when the Spacebar is released. Because
  21817.  SqBtnWndProc will receive WM_CHAR messages only when the control has the
  21818.  input focus, it need not check that it has the input focus when processing
  21819.  the keystrokes.
  21820.  
  21821.  During the WM_PAINT message, SqBtnWndProc calls DrawButton to draw the
  21822.  button. The processing is lengthy but does nothing we didn't see in Chapter
  21823.  5 when exploring the GPI functions. Rather than outlining text to indicate
  21824.  the button's input focus, I decided to display italic text instead.
  21825.  
  21826.  Creating the Square Buttons
  21827.  
  21828.  To test this new window class, we need a program that creates a couple of
  21829.  square 3-D buttons. This is BUTTONS2, shown in Figure 11-8.
  21830.  
  21831.  Figure 11-8.  The BUTTONS2 program.
  21832.  
  21833.    The BUTTONS2 File
  21834.  
  21835.    #--------------------
  21836.    # BUTTONS2 make file
  21837.    #--------------------
  21838.  
  21839.    buttons2.obj : buttons2.c
  21840.         cl -c -G2sw -W3 buttons2.c
  21841.  
  21842.    sqbtn.obj : sqbtn.c
  21843.         cl -c -G2sw -W3 sqbtn.c
  21844.  
  21845.    buttons2.exe : buttons2.obj sqbtn.obj buttons2.def
  21846.         link buttons2 sqbtn, /align:16, NUL, os2, buttons2
  21847.  
  21848.    The BUTTONS2.C File
  21849.  
  21850.    /*-------------------------------------------
  21851.       BUTTONS2.C -- Square Button Demonstration
  21852.      -------------------------------------------*/
  21853.  
  21854.    #define INCL_WIN
  21855.    #define INCL_GPI
  21856.    #include <os2.h>
  21857.  
  21858.    BOOL RegisterSqBtnClass (HAB) ;         // In SQBTN.C
  21859.  
  21860.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  21861.  
  21862.    HAB  hab ;
  21863.  
  21864.    int main (void)
  21865.         {
  21866.         static CHAR  szClientClass[] = "Buttons2" ;
  21867.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  21868.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  21869.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  21870.         HMQ          hmq ;
  21871.         HWND         hwndFrame, hwndClient ;
  21872.         QMSG         qmsg ;
  21873.  
  21874.         hab = WinInitialize (0) ;
  21875.         hmq = WinCreateMsgQueue (hab, 0) ;
  21876.  
  21877.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  21878.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  21879.                                         &flFrameFlags, szClientClass, NULL,
  21880.                                         0L, NULL, 0, &hwndClient) ;
  21881.  
  21882.         WinSendMsg (hwndFrame, WM_SETICON,
  21883.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  21884.                     NULL) ;
  21885.  
  21886.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  21887.              WinDispatchMsg (hab, &qmsg) ;
  21888.  
  21889.         WinDestroyWindow (hwndFrame) ;
  21890.         WinDestroyMsgQueue (hmq) ;
  21891.         WinTerminate (hab) ;
  21892.         return 0 ;
  21893.         }
  21894.  
  21895.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  21896.         {
  21897.         static CHAR  szSqBtnClass[] = "SqBtn",
  21898.                      *szButtonLabel[] = { "Smaller", "Larger" } ;
  21899.         static HWND  hwndFrame, hwndButton[2] ;
  21900.         static SHORT cxClient, cyClient, cxChar, cyChar ;
  21901.         FONTMETRICS  fm ;
  21902.         HPS          hps ;
  21903.         SHORT        id ;
  21904.         RECTL        rcl ;
  21905.  
  21906.         switch (msg)
  21907.              {
  21908.              case WM_CREATE :
  21909.                   hwndFrame = WinQueryWindow (hwnd, QW_PARENT, FALSE) ;
  21910.  
  21911.                   hps = WinGetPS (hwnd) ;
  21912.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  21913.                   cxChar = (SHORT) fm.lAveCharWidth ;
  21914.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  21915.                   WinReleasePS (hps) ;
  21916.  
  21917.                   RegisterSqBtnClass (hab) ;
  21918.  
  21919.                   for (id = 0 ; id < 2 ; id++)
  21920.                        hwndButton[id] = WinCreateWindow (
  21921.                                            hwnd,               // Parent
  21922.                                            "SqBtn",            // Class
  21923.                                            szButtonLabel[id],  // Text
  21924.                                            WS_VISIBLE,         // Style
  21925.                                            0, 0,               // Position
  21926.                                            12 * cxChar,        // Width
  21927.                                            2 * cyChar,         // Height
  21928.                                            hwnd,               // Owner
  21929.                                            HWND_BOTTOM,        // Placement
  21930.                                            id,                 // ID
  21931.                                            NULL,               // Ctrl data
  21932.                                            NULL) ;             // Pres params
  21933.                   return 0 ;
  21934.  
  21935.              case WM_SIZE :
  21936.                   cxClient = SHORT1FROMMP (mp2) ;
  21937.                   cyClient = SHORT2FROMMP (mp2) ;
  21938.  
  21939.                   for (id = 0 ; id < 2 ; id++)
  21940.                        WinSetWindowPos (hwndButton[id], NULL,
  21941.                                  cxClient / 2 + (14 * id - 13) * cxChar,
  21942.                                  (cyClient - 2 * cyChar) / 2,
  21943.                                  0, 0, SWP_MOVE) ;
  21944.                   return 0 ;
  21945.  
  21946.              case WM_COMMAND:
  21947.                   WinQueryWindowRect (hwnd, &rcl) ;
  21948.                   WinMapWindowPoints (hwnd, HWND_DESKTOP, (PPOINTL) &rcl, 2) ;
  21949.  
  21950.                   switch (COMMANDMSG(&msg)->cmd)               // Child ID
  21951.                        {
  21952.                        case 0:                                 // "Smaller"
  21953.                             rcl.xLeft   += cxClient / 20 ;
  21954.                             rcl.xRight  -= cxClient / 20 ;
  21955.                             rcl.yBottom += cyClient / 20 ;
  21956.                             rcl.yTop    -= cyClient / 20 ;
  21957.                             break ;
  21958.  
  21959.                        case 1:                                 // "Larger"
  21960.                             rcl.xLeft   -= cxClient / 20 ;
  21961.                             rcl.xRight  += cxClient / 20 ;
  21962.                             rcl.yBottom -= cyClient / 20 ;
  21963.                             rcl.yTop    += cyClient / 20 ;
  21964.                             break ;
  21965.                        }
  21966.                   WinCalcFrameRect (hwndFrame, &rcl, FALSE) ;
  21967.  
  21968.                   WinSetWindowPos (hwndFrame, NULL,
  21969.                                    (SHORT) rcl.xLeft, (SHORT) rcl.yBottom,
  21970.                                    (SHORT) rcl.xRight - (SHORT) rcl.xLeft,
  21971.                                    (SHORT) rcl.yTop   - (SHORT) rcl.yBottom,
  21972.                                    SWP_MOVE | SWP_SIZE) ;
  21973.                   return 0 ;
  21974.              case WM_ERASEBACKGROUND:
  21975.                   return 1 ;
  21976.              }
  21977.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  21978.         }
  21979.  
  21980.    The BUTTONS2.DEF File
  21981.  
  21982.    ;-------------------------------------
  21983.    ; BUTTONS2.DEF module definition file
  21984.    ;-------------------------------------
  21985.  
  21986.    NAME           BUTTONS2  WINDOWAPI
  21987.  
  21988.    DESCRIPTION    'Square Button Demo (C) Charles Petzold, 1988'
  21989.    PROTMODE
  21990.    HEAPSIZE       1024
  21991.    STACKSIZE      8192
  21992.    EXPORTS        ClientWndProc
  21993.                   SqBtnWndProc
  21994.  
  21995.  
  21996.  You'll notice that the BUTTONS2 make file compiles both SQBTN.C and
  21997.  BUTTONS2.C and links them. The BUTTONS2.DEF file lists both ClientWndProc
  21998.  and SqBtnWndProc in the EXPORTS section.
  21999.  
  22000.  BUTTONS2 is almost identical to BUTTONS1. The only real difference is that
  22001.  ClientWndProc calls RegisterSqBtnClass during the WM_CREATE message. This is
  22002.  the routine in SQBTN.C. The two push buttons are created based on the
  22003.  "SqBtn" class.
  22004.  
  22005.  Figure 11-9 shows BUTTONS2 running under the Presentation Manager.
  22006.  
  22007.  You might like the look of square 3-D push buttons and wonder if you could
  22008.  somehow use them in dialog boxes. We'll do exactly that in Chapter 14.
  22009.  
  22010.  
  22011.  SECTION FOUR      USING RESOURCES
  22012.  
  22013.  Chapter 12  Bitmaps, Icons, Pointers, and Strings
  22014.  ───────────────────────────────────────────────────────────────────────────
  22015.  
  22016.  
  22017.  Until now, our Presentation Manager programs have been missing a few
  22018.  features. The programs haven't included a menu bar across the top of the
  22019.  window, or dialog boxes invoked from menu items, or even a customized icon
  22020.  displayed when the program's window is minimized.
  22021.  
  22022.  Icons, menus, and dialog boxes are all examples of program "resources."
  22023.  Resources are read-only data segments that are stored in a program's .EXE
  22024.  file but that are not part of the program's normal code and data segments.
  22025.  In most cases, resources aren't loaded into memory when OS/2 runs the
  22026.  program──the resources reside on disk in the .EXE file until specifically
  22027.  needed. When resources are loaded into memory, the memory blocks they occupy
  22028.  are read-only segments. Thus OS/2 can allow the resources in memory to be
  22029.  shared by multiple instances of the same program. OS/2 can also discard
  22030.  resources if memory space is needed and then later reload them from the .EXE
  22031.  file.
  22032.  
  22033.  The Presentation Manager BSEDOS.H header file defines 15 resource types, but
  22034.  only the first 10 are commonly used by programs. The identifiers for these
  22035.  resource types begin with the letters RT (as listed in the table below).
  22036.  You can also define your own resource types. This chapter covers bitmaps,
  22037.  icons and pointers, text strings, and programmer-defined resources. Menus
  22038.  and keyboard accelerator tables are covered in Chapter 13 and dialog boxes
  22039.  in Chapter 14.
  22040.  
  22041.  Using resources in your Presentation Manager programs is an option rather
  22042.  than a requirement. If you want, you can instead define menus, dialog boxes,
  22043.  icons, and so forth in the program's normal data segment. However, you'll
  22044.  find that using resources is easier, because the OS/2 Software Development
  22045.  Kit includes several tools to help you create and edit resources. We'll
  22046.  examine one of these tools (the ICONEDIT program) in this chapter.
  22047.  
  22048.     Resource Type              Description
  22049.     RT_POINTER                 Icon or mouse pointer
  22050.     RT_BITMAP                  Bitmap
  22051.     RT_MENU                    Menu template
  22052.     RT_DIALOG                  Dialog box template
  22053.     RT_STRING                  Text string
  22054.     RT_FONTDIR                 Font directory
  22055.     RT_FONT                    Font
  22056.     RT_ACCELTABLE              Keyboard accelerator table
  22057.     RT_RCDATA                  Programmer-defined data
  22058.     RT_MESSAGE                 Message string
  22059.  
  22060.  
  22061.  Basic Concepts
  22062.  
  22063.  A program's .EXE file (or a dynamic link library's .DLL file) is divided
  22064.  into several sections, as shown in Figure 12-1. Following the new .EXE
  22065.  header, each of the program's code and data segments occupies a separate
  22066.  block in the .EXE file. Tables in the header allow OS/2 to identify the
  22067.  beginning of each segment in the .EXE file, the size of the segment, and
  22068.  characteristics of the segment. Resources are organized similarly. They
  22069.  follow the normal code and data segments in the .EXE file and likewise are
  22070.  identified by tables in the header section. You can thus think of an OS/2
  22071.  program as comprising code segments, data segments, and resource segments.
  22072.  
  22073.  Figure 12-1.  The OS/2 .EXE and .DLL file format.
  22074.  
  22075.                                ┌─────────────────┐
  22076.                                │ Old .EXE header │
  22077.                                ├─────────────────┤
  22078.                                │ Optional MS-DOS │
  22079.                                │  .EXE program   │
  22080.                                ├─────────────────┤
  22081.                                │ New .EXE header │
  22082.                                ├─────────────────┤
  22083.                                │     Program     │
  22084.                                │  code and data  │
  22085.                                │     segment     │
  22086.                                ├─────────────────┤
  22087.                                │        ∙        │
  22088.                                │        ∙        │
  22089.                                │        ∙        │
  22090.                                ├─────────────────┤
  22091.                                │Resource segments│
  22092.                                ├─────────────────┤
  22093.                                │        ∙        │
  22094.                                │        ∙        │
  22095.                                │        ∙        │
  22096.                                └─────────────────┘
  22097.  
  22098.  
  22099.  Each resource (with the exception of the RT_STRING and RT_MESSAGE types)
  22100.  occupies a separate segment in the .EXE file. Text and message strings are
  22101.  stored with multiple strings in each segment. Most of the resources are
  22102.  stored in a special format that is unique for that resource type. Your
  22103.  program doesn't need to know the format of the resource in the .EXE file,
  22104.  because the Presentation Manager usually loads the resource and takes care
  22105.  of any translation necessary to put it into a format suitable for use with
  22106.  other Presentation Manager functions.
  22107.  
  22108.  OS/2 Kernel Support of Resources
  22109.  
  22110.  Each resource in the program's .EXE file is identified by a "type ID" and a
  22111.  "name ID," both of which are 16-bit numbers. The identifiers beginning with
  22112.  RT ("resource type") correspond to type IDs of 1 through 15. The
  22113.  Presentation Manager reserves type ID numbers up through 255 for its own
  22114.  use. You are free to use resource type IDs of 256 and above for
  22115.  programmer-defined resource types. The name ID uniquely identifies a
  22116.  particular resource of a particular type. For example, a .EXE file can have
  22117.  several bitmap resources, each of which occupies a different segment in the
  22118.  .EXE file. They all have a type ID of RT_BITMAP, but each bitmap has a
  22119.  different name ID.
  22120.  
  22121.  The OS/2 kernel includes a function called DosGetResource that allows a
  22122.  program to load resources from the .EXE file into memory. (For the moment,
  22123.  let's ignore the problem of how the resources get into the .EXE file in the
  22124.  first place.) Generally, a Presentation Manager program needs to use
  22125.  DosGetResource only for the programmer-defined resources. For the predefined
  22126.  resource types, the Presentation Manager includes other functions to load
  22127.  resources. But given that these Presentation Manager functions ultimately
  22128.  use the DosGetResource function to load the resource into memory, it's
  22129.  worthwhile to understand this function.
  22130.  
  22131.  Before calling DosGetResource, you need several variables:
  22132.  
  22133.    USHORT idType, idName ;
  22134.    SEL    selResource ;
  22135.    PVOID  pResource ;
  22136.  
  22137.  Based on the values of idType and idName, DosGetResource loads a resource
  22138.  from the program's .EXE file into memory and returns the segment selector of
  22139.  the memory block containing the resource:
  22140.  
  22141.    DosGetResource (NULL, idType, idName, &selResource) ;
  22142.  
  22143.  You then convert the segment selector to a long (or far) address with the
  22144.  MAKEP macro:
  22145.  
  22146.    pResource = MAKEP (selResource, 0) ;
  22147.  
  22148.  The memory segment belongs to your process. You can use pResource as a
  22149.  normal far pointer to access the resource. But because the memory segment is
  22150.  read-only, you can't write to it. Otherwise, it's a normal memory segment.
  22151.  You can use DosSizeSeg to find the size of the segment and DosFreeSeg to
  22152.  free the segment from memory.
  22153.  
  22154.  When the first parameter of DosGetResource is NULL, OS/2 loads the resource
  22155.  from the program's .EXE file. You can also load a resource from a dynamic
  22156.  link library. Let's assume the dynamic link library containing the resource
  22157.  is named MYLIB.DLL. You first need to define a variable of type HMODULE to
  22158.  hold the module handle:
  22159.  
  22160.    HMODULE hmod ;
  22161.  
  22162.  You then call DosLoadModule to obtain the module handle:
  22163.  
  22164.    DosLoadModule (NULL, 0, "MYLIB", &hmod) ;
  22165.  
  22166.  The first parameter of DosLoadModule can optionally be set to the address of
  22167.  a buffer area that OS/2 uses if it can't obtain the module handle.
  22168.  Generally, OS/2 will fill this buffer by using the name of the module that
  22169.  contributed to the failure of the function. The second parameter is the
  22170.  length of this buffer.
  22171.  
  22172.  After you obtain the module handle, you can use it as the first parameter to
  22173.  DosGetResource to load a resource from the dynamic link library:
  22174.  
  22175.    DosGetResource (hmod, usTypeID, usNameID, &selResource) ;
  22176.  
  22177.  After you free the resource segment and no longer need it, you can free the
  22178.  module handle:
  22179.  
  22180.    DosFreeModule (hmod) ;
  22181.  
  22182.  I'll discuss the use of dynamic link libraries in more depth in Chapter
  22183.  16.
  22184.  
  22185.  The OS/2 Kernel Message Facility
  22186.  
  22187.  Although the DosGetResource function allows you to load resources of any
  22188.  type into memory, the only use of resources within the OS/2 kernel is for
  22189.  "message strings." These are text strings that contain replaceable
  22190.  parameters to display messages from OS/2 and the various OS/2 commands. The
  22191.  MKMSGF.EXE program creates a binary file with the extension .MSG based on an
  22192.  ASCII file containing message texts and codes. The MSGBIND.EXE program then
  22193.  adds these messages as resources to a program's .EXE file. An OS/2 program
  22194.  can either access a message from the .MSG file or load the message from its
  22195.  own .EXE file using the DosGetMessage function. If the message text has
  22196.  replaceable parameters (indicated by %1, %2, and so forth), the
  22197.  DosGetMessage function can insert other text (such as filenames) into the
  22198.  message text. Because the messages aren't in the program's normal data
  22199.  segments, OS/2 programs that use this messaging facility can be customized
  22200.  more easily for foreign-language markets.
  22201.  
  22202.  The Resource Script
  22203.  
  22204.  Although Presentation Manager programs can use the OS/2 message facility,
  22205.  they also need resources of other types. To add these resources to a
  22206.  program's .EXE file, the programmer first prepares an ASCII file called a
  22207.  "resource script." By convention, this file has the extension .RC. The
  22208.  resource script file includes some resources in an ASCII format and can also
  22209.  reference other files that contain binary resources, such as icons, mouse
  22210.  pointers, and bitmaps. Figure 12-2 shows a sample resource script named
  22211.  SAMPLE.RC that contains a reference to an icon file, a menu template, a
  22212.  keyboard accelerator table, and a string table. It's not important right
  22213.  now that you understand the format of the statements in this file. We'll
  22214.  cover the details as we study each resource in depth.
  22215.  
  22216.  The keywords POINTER, MENU, ACCELTABLE, and STRINGTABLE all correspond to
  22217.  predefined resource types. The SAMPLE.ICO file referenced by the POINTER
  22218.  statement is a separate binary file containing a bitmap of the program's
  22219.  icon. You'll also note that the file contains several identifiers beginning
  22220.  with the letters ID, IDM, IDS, and IDD. Some of these are resource name IDs.
  22221.  They are all constants defined in a separate header file, SAMPLE.H, shown in
  22222.  Figure 12-3. This header file must also be included in the program's C
  22223.  source code file so that the program can refer to these resources using the
  22224.  identifiers.
  22225.  
  22226.  Figure 12-2.  The SAMPLE.RC file.
  22227.  
  22228.    /*------------------------------------------
  22229.       SAMPLE.RC -- Sample Resource Script File
  22230.      ------------------------------------------*/
  22231.  
  22232.    #include <os2.h>
  22233.    #include "sample.h"
  22234.  
  22235.    POINTER ID_RESOURCE sample.ico
  22236.  
  22237.    MENU ID_RESOURCE
  22238.         {
  22239.         SUBMENU "~File",              IDM_FILE
  22240.              {
  22241.              MENUITEM "~New",              IDM_NEW
  22242.              MENUITEM "~Open...",          IDM_OPEN
  22243.              MENUITEM "~Save",             IDM_SAVE
  22244.              MENUITEM "Save ~As...",       IDM_SAVEAS
  22245.              MENUITEM SEPARATOR
  22246.              MENUITEM "E~xit",             IDM_EXIT
  22247.              MENUITEM "A~bout Sample...",  IDM_ABOUT
  22248.              }
  22249.         SUBMENU "~Edit",              IDM_EDIT
  22250.              {
  22251.              MENUITEM "~Undo\tAlt+BkSp",   IDM_UNDO
  22252.              MENUITEM SEPARATOR
  22253.              MENUITEM "Cu~t\tDel",         IDM_CUT
  22254.              MENUITEM "~Copy\tCtrl+Ins",   IDM_COPY
  22255.              MENUITEM "~Paste\tShift+Ins", IDM_PASTE
  22256.              MENUITEM "C~lear\tShift+Del", IDM_CLEAR
  22257.              }
  22258.         MENUITEM "F1=Help",      IDM_HELP, MIS_HELP | MIS_BUTTONSEPARATOR
  22259.         }
  22260.  
  22261.    ACCELTABLE ID_RESOURCE
  22262.         {
  22263.         VK_BACKSPACE,  IDM_UNDO,  VIRTUALKEY, ALT
  22264.         VK_DELETE,     IDM_CUT,   VIRTUALKEY
  22265.         VK_INSERT,     IDM_COPY,  VIRTUALKEY, CONTROL
  22266.         VK_INSERT,     IDM_PASTE, VIRTUALKEY, SHIFT
  22267.         VK_DELETE,     IDM_CLEAR, VIRTUALKEY, SHIFT
  22268.         }
  22269.  
  22270.    STRINGTABLE
  22271.         {
  22272.         IDS_APPNAME,     "Sample"
  22273.         IDS_TITLEBAR,    "Sample Titlebar Text"
  22274.         }
  22275.  
  22276.  Figure 12-3.  The SAMPLE.H file.
  22277.  
  22278.    /*-------------------------------------------------
  22279.       SAMPLE.H -- Sample Header File for Resource IDs
  22280.      -------------------------------------------------*/
  22281.  
  22282.    #define ID_RESOURCE 1
  22283.  
  22284.         /*-----------------
  22285.            IDM -- Menu IDs
  22286.           -----------------*/
  22287.  
  22288.    #define IDM_FILE    10
  22289.    #define IDM_NEW     11
  22290.    #define IDM_OPEN    12
  22291.    #define IDM_SAVE    13
  22292.    #define IDM_SAVEAS  14
  22293.    #define IDM_EXIT    15
  22294.    #define IDM_ABOUT   16
  22295.  
  22296.    #define IDM_EDIT    20
  22297.    #define IDM_UNDO    21
  22298.    #define IDM_CUT     22
  22299.    #define IDM_COPY    23
  22300.    #define IDM_PASTE   24
  22301.    #define IDM_CLEAR   25
  22302.  
  22303.    #define IDM_HELP    30
  22304.  
  22305.         /*-------------------
  22306.            IDS -- String IDs
  22307.           -------------------*/
  22308.  
  22309.    #define IDS_APPNAME      1
  22310.    #define IDS_TITLEBAR     2
  22311.  
  22312.  
  22313.  The Resource Compiler
  22314.  
  22315.  The ASCII resource script must be compiled to a binary form. By convention,
  22316.  the extension of the compiled resource file is .RES. The compiled resources
  22317.  must then be added to the program's .EXE file or to the dynamic link
  22318.  library's .DLL file. Both of these jobs──compiling the resources and
  22319.  adding them to the .EXE file──are handled by the RC.EXE resource compiler.
  22320.  You can do them separately or in a single step.
  22321.  
  22322.  Compiling the Resources
  22323.  To compile the ASCII .RC file into a binary .RES file without adding the
  22324.  resources to a .EXE file, use the command
  22325.  
  22326.    RC -r SAMPLE
  22327.  
  22328.  The .RC extension on SAMPLE is assumed. This command creates a SAMPLE.RES
  22329.  file.
  22330.  
  22331.  Adding the Resources to .EXE
  22332.  To add the compiled resources to the .EXE file, use the command
  22333.  
  22334.    RC SAMPLE.RES
  22335.  
  22336.  This adds the compiled resources in SAMPLE.RES to the SAMPLE.EXE file. (If
  22337.  the .EXE file contains any resources already, they are replaced with the new
  22338.  resources.) Optionally, you can include the name of the .EXE file if it's
  22339.  different from the .RES file:
  22340.  
  22341.    RC SAMPLE.RES MYEXE.EXE
  22342.  
  22343.  The .RES extension is required in this form of the command to differentiate
  22344.  it from the next form of the command.
  22345.  
  22346.  Compiling and Adding as a Single Step
  22347.  You can do both jobs in one step with the command
  22348.  
  22349.    RC SAMPLE
  22350.  
  22351.  The .RC extension on SAMPLE is assumed. This command compiles the resources
  22352.  to create a SAMPLE.RES file and then adds the resources to the SAMPLE.EXE
  22353.  file. If the name of the .EXE file is different from the .RC file, you can
  22354.  use
  22355.  
  22356.    RC SAMPLE MYEXE.EXE
  22357.  
  22358.  Presentation Manager programmers usually set up their make files to compile
  22359.  the resources and add them to the .EXE file in two separate steps. This
  22360.  results in a faster edit-make-run cycle because compiling the resources
  22361.  often takes much longer than adding them to the .EXE file. During
  22362.  development of a program, you'll generally make more changes to the C source
  22363.  code file than to the resource script file. You don't need to recompile the
  22364.  resources. Instead, you want to compile the C source code file, link it, and
  22365.  add the compiled resources. Typically, a make file for a program containing
  22366.  resources looks like SAMPLE, shown in Figure 12-4.
  22367.  
  22368.  Figure 12-4.  The SAMPLE make file.
  22369.  
  22370.    #--------------------
  22371.    # SAMPLE make file
  22372.    #--------------------
  22373.  
  22374.    sample.obj : sample.c sample.h
  22375.         cl -c -G2sw -W2 sample.c
  22376.  
  22377.    sample.res : sample.rc sample.h sample.ico
  22378.         rc -r sample
  22379.  
  22380.    sample.exe : sample.obj sample.def sample.res
  22381.         link sample, /align:16, NUL, os2, sample
  22382.         rc sample.res
  22383.  
  22384.  
  22385.  The first compile step indicates that SAMPLE.C and SAMPLE.H are dependent
  22386.  files for the creation of SAMPLE.OBJ. The header file defines constants used
  22387.  by the program to reference the resources. The second step in the make file
  22388.  runs RC.EXE with the -r parameter to compile the ASCII SAMPLE.RC file into a
  22389.  binary SAMPLE.RES file. This step also requires both SAMPLE.H and
  22390.  SAMPLE.ICO. The third step is executed if SAMPLE.OBJ, SAMPLE.DEF, or
  22391.  SAMPLE.RES is updated. This links the program and then adds the resources to
  22392.  the .EXE file using RC.EXE again.
  22393.  
  22394.  If you make a lot of changes to the resource script file, you'll probably
  22395.  want to avoid re-linking each time. Adding a couple of extra lines to the
  22396.  make file (shown in Figure 12-5) will speed things up.
  22397.  
  22398.  Figure 12-5.  A better SAMPLE make file for a program with resources.
  22399.  
  22400.    #--------------------
  22401.    # SAMPLE make file
  22402.    #--------------------
  22403.  
  22404.    sample.obj : sample.c sample.h
  22405.         cl -c -G2sw -W2 sample.c
  22406.  
  22407.    sample.res : sample.rc sample.h sample.ico
  22408.         rc -r sample
  22409.  
  22410.    sample.exe : sample.obj sample.def
  22411.         link sample, /align:16, NUL, os2, sample
  22412.         rc sample.res
  22413.  
  22414.    sample.exe : sample.res
  22415.         rc sample.res
  22416.  
  22417.  
  22418.  Note that the LINK step is not run if only SAMPLE.RES has changed. Instead,
  22419.  the resources in the .EXE file are replaced in the last RC step.
  22420.  
  22421.  Figure 12-6 shows the general procedure for creating a Presentation
  22422.  Manager program that uses resources. You create the source code files listed
  22423.  in the five boxes across the top of the diagram. The rest of the process is
  22424.  handled by the make file.
  22425.  
  22426.  Figure 12-6.  The process of creating a Presentation Manager program that
  22427.               uses resources.
  22428.  
  22429.  ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐
  22430.  │   .DEF   │   │    .C    │   │    .H    │   │   .RC    │   │.ICO,.CUR,│
  22431.  │  module  │   │  source  │   │  header  │   │ resource │   │.BMP,.DLG │
  22432.  │definition│   │   file   │   │   file   │   │  script  │   │  files   │
  22433.  │   file   │   │          │   │          │   │          │   │          │
  22434.  └────┬─────┘   └────┬─────┘   └────┬─────┘   └─────┬────┘   └────┬─────┘
  22435.       │              └────┐      ┌──┴──────────┐    │   ┌─────────┘
  22436.       │                 ┌────────┐           │    │   │
  22437.       │                 │          │           │    │   │
  22438.       │                 │  CL.EXE  │           │    │   │
  22439.       │                 │ compiler │           │    │   │
  22440.       │                 │          │           │    │   │
  22441.       │                 └────┬─────┘           │    │   │
  22442.       │                      │                 │    │   │
  22443.       │                 ┌─────────┐           │    │   │
  22444.       │                 │   .OBJ   │           │    │   │
  22445.       │                 │  object  │           │    │   │
  22446.       │                 │   file   │           │    │   │
  22447.       │                 │          │           │    │   │
  22448.       └───┐             └────┬─────┘           │    │   │
  22449.           │      ┌───────────┘                 │    │   │
  22450.         ┌────────┐                          ┌───────┐
  22451.         │          │                          │  RC.EXE  │
  22452.         │ LINK.EXE │                          │ compiled │
  22453.         │  linker  │                          │resources │
  22454.         │          │                          │          │
  22455.         └─────┬────┘                          └────┬─────┘
  22456.         ┌─────────┐                          ┌─────────┐
  22457.         │   .EXE   │                          │   .RES   │
  22458.         │ without  │                          │ compiled │
  22459.         │resources │                          │resources │
  22460.         │          │                          │          │
  22461.         └─────┬────┘                          └────┬─────┘
  22462.               └──────────────┐      ┌──────────────┘
  22463.                            ┌────────┐
  22464.                            │  RC.EXE  │
  22465.                            │ resource │
  22466.                            │ compiler │
  22467.                            │          │
  22468.                            └────┬─────┘
  22469.                            ┌─────────┐
  22470.                            │   .EXE   │
  22471.                            │   with   │
  22472.                            │resources │
  22473.                            │          │
  22474.                            └──────────┘
  22475.  
  22476.  
  22477.  Bitmap Resources
  22478.  
  22479.  We learned about bitmaps in Chapter 6. A bitmap is a block of memory
  22480.  organized by rows and columns where the bits represent a graphical image. In
  22481.  a monochrome bitmap, each bit in the bitmap corresponds to a display pixel.
  22482.  A color bitmap requires two or more bits per pixel to contain color
  22483.  information. The Presentation Manager and Graphics Programming Interface
  22484.  include several functions for creating, manipulating, and displaying
  22485.  bitmaps. Although we were able to define a bitmap in a program by a series
  22486.  of bytes in Chapter 6, it's usually much easier to create the bitmap in the
  22487.  Presentation Manager ICONEDIT program.
  22488.  
  22489.  Creating a Bitmap in ICONEDIT
  22490.  
  22491.  ICONEDIT is a Presentation Manager program that lets you create icons, mouse
  22492.  pointers, and monochrome bitmaps. You draw the image using the mouse. To
  22493.  create a new bitmap in ICONEDIT, choose New from the File menu, choose
  22494.  Bitmap from the dialog box, and enter a width and height in pixels.
  22495.  
  22496.  The bitmap is initially all white, which means that every bit is set to 1.
  22497.  You use the mouse buttons to color the pixels. From the menu you can display
  22498.  a grid and select different pen sizes. When you're finished, choose Save As
  22499.  from the File menu and enter a filename. ICONEDIT adds a .BMP extension,
  22500.  which is standard for a bitmap. A header section in the .BMP file contains
  22501.  the height and width of the bitmap, the number of color planes (which equals
  22502.  1 for a monochrome bitmap), the number of color bits per pixel (also 1), and
  22503.  the color table. More complete documentation on using ICONEDIT is included
  22504.  in the OS/2 Programmer's Toolkit.
  22505.  
  22506.  Using the Bitmap Resource in a Program
  22507.  
  22508.  After you create a bitmap file, you need to reference the bitmap filename in
  22509.  a resource script and assign the bitmap a name ID. In the program you use
  22510.  that name ID to load the bitmap into memory. Assuming the program's name is
  22511.  SAMPLE and the bitmap file is SAMPLE.BMP, the SAMPLE.RC resource script file
  22512.  contains the following statement to reference the bitmap file:
  22513.  
  22514.    BITMAP idName sample.bmp
  22515.  
  22516.  The idName value is simply an unsigned 16-bit number or an identifier
  22517.  defined in a header file #define statement. BITMAP is a resource compiler
  22518.  keyword. When you compile the resource script and add the resources to the
  22519.  program's .EXE file, the .EXE file contains a resource with a type ID of
  22520.  RT_BITMAP and a name ID of idName.
  22521.  
  22522.  A program loads a bitmap resource into memory using the GpiLoadBitmap
  22523.  function. First, the program must define a variable of type HBITMAP:
  22524.  
  22525.    HBITMAP hbm ;
  22526.  
  22527.  The GpiLoadBitmap function returns a handle to the bitmap:
  22528.  
  22529.    hbm = GpiLoadBitmap (hps, hmod, idName, lWidth, lHeight) ;
  22530.  
  22531.  The GpiLoadBitmap function works only with resources of the RT_BITMAP type.
  22532.  The name ID identifies the particular RT_BITMAP resource.
  22533.  
  22534.  The hps parameter to GpiLoadBitmap is a handle to a presentation space. The
  22535.  Presentation Manager uses this handle for two purposes. The first is to
  22536.  convert the colors of the bitmap into a form suitable for the device
  22537.  context. (Because we're working with a monochrome bitmap in this example,
  22538.  the bitmap can be loaded without any color conversion.) The second purpose
  22539.  of the presentation space handle is to allow GPI to store the bitmap in part
  22540.  of the device's memory. For example, the Enhanced Graphics Adapter has a
  22541.  maximum of 256 KB of memory, but only 112 KB are used for display purposes.
  22542.  The Presentation Manager can store the bitmap in free display memory.
  22543.  Graphics coprocessors on some video boards can more efficiently display
  22544.  bitmaps if they are stored in an unused region of display memory. For a
  22545.  monochrome bitmap, you can set the hps parameter to NULL, and the
  22546.  Presentation Manager will store the bitmap in normal memory.
  22547.  
  22548.  The hmod parameter is set to NULL if you're loading the bitmap from the
  22549.  program's .EXE file. Otherwise, this is the module handle of a dynamic link
  22550.  library file. The idName is the ID number of the bitmap you want to load.
  22551.  The lWidth and lHeight parameters indicate the resultant size of the bitmap
  22552.  when the Presentation Manager loads it into memory. The bitmap can be
  22553.  compressed or stretched. If you set both parameters to 0L, the bitmap will
  22554.  retain the size you specified when you created the bitmap file in ICONEDIT.
  22555.  
  22556.  Before the program terminates, you delete the bitmap from memory:
  22557.  
  22558.    GpiDeleteBitmap (hbm) ;
  22559.  
  22560.  A Sample Program
  22561.  
  22562.  The LOADBMP1 program, shown in Figure 12-7, demonstrates how to include a
  22563.  bitmap as a resource in a program, load the bitmap into memory, and display
  22564.  it on the client window. The LOADBMP.BMP file is a 64-by-32-pixel bitmap
  22565.  file that was created in ICONEDIT in about 10 seconds (and looks it).
  22566.  
  22567.  Figure 12-7.  The LOADBMP1 program.
  22568.  
  22569.    The LOADBMP1 File
  22570.  
  22571.    #--------------------
  22572.    # LOADBMP1 make file
  22573.    #--------------------
  22574.  
  22575.    loadbmp1.obj : loadbmp1.c loadbmp.h
  22576.         cl -c -G2sw -W3 loadbmp1.c
  22577.  
  22578.    loadbmp.res : loadbmp.rc loadbmp.h loadbmp.bmp
  22579.         rc -r loadbmp
  22580.  
  22581.    loadbmp1.exe : loadbmp1.obj loadbmp1.def
  22582.         link loadbmp1, /align:16, NUL, os2, loadbmp1
  22583.         rc loadbmp.res loadbmp1.exe
  22584.  
  22585.    loadbmp1.exe : loadbmp.res
  22586.         rc loadbmp.res loadbmp1.exe
  22587.  
  22588.    The LOADBMP1.C File
  22589.  
  22590.    /*----------------------------------------------------
  22591.       LOADBMP1.C -- Loads a Bitmap Resource and Draws it
  22592.      ----------------------------------------------------*/
  22593.  
  22594.    #define INCL_WIN
  22595.    #include <os2.h>
  22596.    #include "loadbmp.h"
  22597.  
  22598.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  22599.  
  22600.    int main (void)
  22601.         {
  22602.         static CHAR  szClientClass [] = "LoadBmp1" ;
  22603.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  22604.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  22605.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  22606.  
  22607.         HAB          hab ;
  22608.         HMQ          hmq ;
  22609.         HWND         hwndFrame, hwndClient ;
  22610.         QMSG         qmsg ;
  22611.  
  22612.         hab = WinInitialize (0) ;
  22613.         hmq = WinCreateMsgQueue (hab, 0) ;
  22614.  
  22615.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  22616.  
  22617.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  22618.                                         &flFrameFlags, szClientClass, NULL,
  22619.                                         0L, NULL, 0, &hwndClient) ;
  22620.  
  22621.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  22622.              WinDispatchMsg (hab, &qmsg) ;
  22623.  
  22624.         WinDestroyWindow (hwndFrame) ;
  22625.         WinDestroyMsgQueue (hmq) ;
  22626.         WinTerminate (hab) ;
  22627.         return 0 ;
  22628.         }
  22629.  
  22630.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  22631.         {
  22632.         static SHORT cxClient, cyClient ;
  22633.         HBITMAP      hbm ;
  22634.         HPS          hps ;
  22635.         POINTL       ptl ;
  22636.  
  22637.         switch (msg)
  22638.              {
  22639.              case WM_SIZE:
  22640.                   cxClient = SHORT1FROMMP (mp2) ;
  22641.                   cyClient = SHORT2FROMMP (mp2) ;
  22642.                   return 0 ;
  22643.  
  22644.              case WM_PAINT:
  22645.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  22646.                   GpiErase (hps) ;
  22647.  
  22648.                   hbm = GpiLoadBitmap (hps, NULL, IDB_HELLO,
  22649.                                        (LONG) cxClient, (LONG) cyClient) ;
  22650.                   if (hbm)
  22651.  
  22652.                        {
  22653.                        ptl.x = 0 ;
  22654.                        ptl.y = 0 ;
  22655.  
  22656.                        WinDrawBitmap (hps, hbm, NULL, &ptl,
  22657.                                       CLR_NEUTRAL, CLR_BACKGROUND, DBM_NORMAL)
  22658.  
  22659.                        GpiDeleteBitmap (hbm) ;
  22660.                        }
  22661.                   WinEndPaint (hps) ;
  22662.                   return 0 ;
  22663.              }
  22664.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  22665.         }
  22666.  
  22667.    The LOADBMP.H File
  22668.  
  22669.    /*-----------------------
  22670.       LOADBMP.H header file
  22671.      -----------------------*/
  22672.  
  22673.    #define IDB_HELLO 55
  22674.  
  22675.    The LOADBMP.RC File
  22676.  
  22677.    /*---------------------------------
  22678.       LOADBMP.RC resource script file
  22679.      ---------------------------------*/
  22680.  
  22681.    #include "loadbmp.h"
  22682.  
  22683.    BITMAP IDB_HELLO loadbmp.bmp
  22684.  
  22685.    The LOADBMP.BMP File
  22686.  
  22687.    The LOADBMP1.DEF File
  22688.  
  22689.    ;-------------------------------------
  22690.    ; LOADBMP1.DEF module definition file
  22691.    ;-------------------------------------
  22692.  
  22693.    NAME           LOADBMP1  WINDOWAPI
  22694.  
  22695.    DESCRIPTION    'Loads Bitmap Resource and Draws it (C) Charles Petzold, 198
  22696.    PROTMODE
  22697.    HEAPSIZE       1024
  22698.    STACKSIZE      8192
  22699.    EXPORTS        ClientWndProc
  22700.  
  22701.  
  22702.  I decided to give the bitmap a name ID of 55. The identifier IDB_HELLO is
  22703.  defined in LOADBMP.H for this purpose:
  22704.  
  22705.    #define IDB_HELLO 55
  22706.  
  22707.  The IDB prefix stands for "ID for a bitmap."
  22708.  
  22709.  This statement in the LOADBMP.RC resource script file references the file
  22710.  containing the bitmap:
  22711.  
  22712.    BITMAP IDB_HELLO loadbmp.bmp
  22713.  
  22714.  The LOADBMP.RC resource script is compiled by the following command in the
  22715.  make file:
  22716.  
  22717.    rc -r loadbmp
  22718.  
  22719.  The resource compiler creates a binary LOADBMP.RES file that contains the
  22720.  entire LOADBMP.BMP file. When RC.EXE is run the second time in the make
  22721.  file, the bitmap resource in LOADBMP.RES is added to the LOADBMP1.EXE file:
  22722.  
  22723.    rc loadbmp.res loadbmp1.exe
  22724.  
  22725.  Following this step, the LOADBMP1.EXE file includes a resource segment
  22726.  containing the entire bitmap. The header section of the .EXE file identifies
  22727.  the resource type ID (RT_BITMAP) and name ID (55). The program can then get
  22728.  access to that resource.
  22729.  
  22730.  During the WM_PAINT message in LOADBMP1.C, the bitmap resource is loaded
  22731.  into memory and stretched to fill the size of the client window:
  22732.  
  22733.    hbm = GpiLoadBitmap (hps, NULL, IDB_HELLO,
  22734.                        (LONG) cxClient, (LONG) cyClient) ;
  22735.  
  22736.  Note that the second parameter is set to NULL to indicate that the resource
  22737.  is part of the program's .EXE file. The IDB_HELLO identifier is the name ID
  22738.  of the resource defined in LOADBMP.H. (Obviously, I could have dispensed
  22739.  with the LOADBMP.H file in this example and used 55 in place of IDB_HELLO in
  22740.  both the resource script and the program. But for more complex resources
  22741.  such as menus and dialog boxes, the header file becomes very important, so
  22742.  we might as well get accustomed to using it.)
  22743.  
  22744.  GpiLoadBitmap returns NULL if the bitmap can't be loaded into memory. The
  22745.  rest of the WM_PAINT logic continues only if hbm isn't NULL:
  22746.  
  22747.    if (hbm)
  22748.         {
  22749.         ptl.x = 0 ;
  22750.         ptl.y = 0 ;
  22751.  
  22752.         WinDrawBitmap (hps, hbm, NULL, &ptl,
  22753.                        CLR_NEUTRAL, CLR_BACKGROUND, DBM_NORMAL) ;
  22754.  
  22755.         GpiDeleteBitmap (hbm) ;
  22756.         }
  22757.  
  22758.  This draws the bitmap on the client window and then deletes it from memory.
  22759.  The LOADBMP1 window is shown in Figure 12-8.
  22760.  
  22761.  An Alternative Approach to Loading Bitmaps
  22762.  
  22763.  LOADBMP1 loads the bitmap and then deletes it whenever it needs to repaint
  22764.  the client window. Another approach is to load the bitmap during the
  22765.  WM_CREATE message, keep it in memory for the duration of the program, and
  22766.  then delete it during the WM_DESTROY message.
  22767.  
  22768.  The LOADBMP2 program, shown in Figure 12-9, illustrates this approach. The
  22769.  LOADBMP.H, LOADBMP.RC, and LOADBMP.BMP files from Figure 12-7 are also
  22770.  required to compile the program.
  22771.  
  22772.  Figure 12-9.  The LOADBMP2 program.
  22773.  
  22774.    The LOADBMP2 File
  22775.  
  22776.    #--------------------
  22777.    # LOADBMP2 make file
  22778.    #--------------------
  22779.  
  22780.    loadbmp2.obj : loadbmp2.c loadbmp.h
  22781.         cl -c -G2sw -W3 loadbmp2.c
  22782.  
  22783.    loadbmp.res : loadbmp.rc loadbmp.h loadbmp.bmp
  22784.         rc -r loadbmp
  22785.  
  22786.    loadbmp2.exe : loadbmp2.obj loadbmp2.def
  22787.         link loadbmp2, /align:16, NUL, os2, loadbmp2
  22788.         rc loadbmp.res loadbmp2.exe
  22789.  
  22790.    loadbmp2.exe : loadbmp.res
  22791.         rc loadbmp.res loadbmp2.exe
  22792.  
  22793.    The LOADBMP2.C File
  22794.  
  22795.    /*----------------------------------------------------
  22796.       LOADBMP2.C -- Loads a Bitmap Resource and Draws it
  22797.      ----------------------------------------------------*/
  22798.  
  22799.    #define INCL_WIN
  22800.    #include <os2.h>
  22801.    #include "loadbmp.h"
  22802.  
  22803.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  22804.  
  22805.    int main (void)
  22806.         {
  22807.         static CHAR  szClientClass [] = "LoadBmp2" ;
  22808.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU |
  22809.                                     FCF_SIZEBORDER    | FCF_MINMAX  |
  22810.                                     FCF_SHELLPOSITION | FCF_TASKLIST ;
  22811.         HAB          hab ;
  22812.         HMQ          hmq ;
  22813.         HWND         hwndFrame, hwndClient ;
  22814.         QMSG         qmsg ;
  22815.  
  22816.         hab = WinInitialize (0) ;
  22817.         hmq = WinCreateMsgQueue (hab, 0) ;
  22818.  
  22819.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  22820.  
  22821.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  22822.                                         &flFrameFlags, szClientClass, NULL,
  22823.                                         0L, NULL, 0, &hwndClient) ;
  22824.  
  22825.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  22826.              WinDispatchMsg (hab, &qmsg) ;
  22827.  
  22828.         WinDestroyWindow (hwndFrame) ;
  22829.         WinDestroyMsgQueue (hmq) ;
  22830.         WinTerminate (hab) ;
  22831.         return 0 ;
  22832.         }
  22833.  
  22834.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  22835.         {
  22836.         static HBITMAP hbm ;
  22837.         HPS            hps ;
  22838.         RECTL          rcl ;
  22839.  
  22840.         switch (msg)
  22841.              {
  22842.              case WM_CREATE:
  22843.                   hps = WinGetPS (hwnd) ;
  22844.                   hbm = GpiLoadBitmap (hps, NULL, IDB_HELLO, 0L, 0L) ;
  22845.                   WinReleasePS (hps) ;
  22846.                   return 0 ;
  22847.  
  22848.              case WM_PAINT:
  22849.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  22850.                   GpiErase (hps) ;
  22851.  
  22852.                   WinQueryWindowRect (hwnd, &rcl) ;
  22853.  
  22854.                   if (hbm)
  22855.                        WinDrawBitmap (hps, hbm, NULL, (PPOINTL) &rcl,
  22856.                                       CLR_NEUTRAL, CLR_BACKGROUND, DBM_STRETCH
  22857.  
  22858.                   WinEndPaint (hps) ;
  22859.                   return 0 ;
  22860.  
  22861.              case WM_DESTROY:
  22862.                   if (hbm)
  22863.                        GpiDeleteBitmap (hbm) ;
  22864.                   return 0 ;
  22865.              }
  22866.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  22867.         }
  22868.  
  22869.    The LOADBMP2.DEF File
  22870.  
  22871.    ;-------------------------------------
  22872.    ; LOADBMP2.DEF module definition file
  22873.    ;-------------------------------------
  22874.  
  22875.    NAME           LOADBMP2  WINDOWAPI
  22876.  
  22877.    DESCRIPTION    'Loads Bitmap Resource and Draws it (C) Charles Petzold, 198
  22878.    PROTMODE
  22879.    HEAPSIZE       1024
  22880.    STACKSIZE      8192
  22881.    EXPORTS        ClientWndProc
  22882.  
  22883.  
  22884.  In LOADBMP2.C, the last two parameters of GpiLoadBitmap are set to 0, so the
  22885.  bitmap isn't stretched when loaded into memory:
  22886.  
  22887.    case WM_CREATE:
  22888.         hps = WinGetPS (hwnd) ;
  22889.         hbm = GpiLoadBitmap (hps, NULL, IDB_HELLO, 0L, 0L) ;
  22890.         WinReleasePS (hps) ;
  22891.         return 0 ;
  22892.  
  22893.  The WM_PAINT logic stretches the bitmap in the WinDrawBitmap function:
  22894.  
  22895.    WinQueryWindowRect (hwnd, &rcl) ;
  22896.  
  22897.    if (hbm)
  22898.         WinDrawBitmap (hps, hbm, NULL, (PPOINTL) &rcl,
  22899.                        CLR_NEUTRAL, CLR_BACKGROUND, DBM_STRETCH) ;
  22900.  
  22901.  LOADBMP2 deletes the bitmap while processing the WM_DESTROY message:
  22902.  
  22903.    case WM_DESTROY:
  22904.         if (hbm)
  22905.              GpiDeleteBitmap (hbm) ;
  22906.         return 0 ;
  22907.  
  22908.  It's OK to handle small bitmaps in this way, but you should be leery of
  22909.  keeping large bitmaps in memory for long periods. Although the Presentation
  22910.  Manager can discard bitmaps from memory and reload them from the program's
  22911.  .EXE file, common courtesy requires that you not be piggy with memory space.
  22912.  
  22913.  
  22914.  Icons and Mouse Pointers
  22915.  
  22916.  Icons and customized mouse pointers are identical in structure and are often
  22917.  interchangeable. In fact, both icon and pointer resources are stored in the
  22918.  program's .EXE file with a resource type of RT_POINTER. Some Presentation
  22919.  Manager functions that seemingly apply to pointers (WinLoadPointer and
  22920.  WinDestroyPointer, for example) can also be used with icons.
  22921.  
  22922.  You can use icons in your program in two ways: as a symbolic representation
  22923.  of the program when the program's top-level window is minimized, and as
  22924.  little pictures you can draw on the program's client window. You can create
  22925.  and use a customized mouse pointer in your program to substitute for the
  22926.  default mouse pointer. For example, the ICONEDIT program itself has a
  22927.  customized mouse pointer that looks like a paintbrush.
  22928.  
  22929.  Designing Icons and Pointers
  22930.  
  22931.  You use ICONEDIT to create icons and pointers. ICONEDIT saves icon files
  22932.  with a .ICO extension and pointer files with a .PTR extension. When you
  22933.  create an icon or pointer in ICONEDIT, it's important that you understand
  22934.  how these images are used by the Presentation Manager.
  22935.  
  22936.  The first issue is resolution. The Presentation Manager always displays an
  22937.  icon or pointer in a pixel dimension that is appropriate for the video
  22938.  display adapter on which the Presentation Manager is running. A program can
  22939.  obtain this dimension by calling the WinQuerySysValue function with
  22940.  parameters of SV_CXICON, SV_CYICON, SV_CXPOINTER, and SV_CYPOINTER. For most
  22941.  video display adapters, the Presentation Manager uses the same dimensions
  22942.  for both icons and pointers. The following table shows these dimensions for
  22943.  the most common display adapters:
  22944.  
  22945.     Width        Height       Display Adapter
  22946.     32           16           Color Graphics Adapter (640 x 200)
  22947.                               Enhanced Graphics Adapter (640 x 200)
  22948.     32           32           Enhanced Graphics Adapter (640 x 350)
  22949.                               Video Graphics Array (640 x 480)
  22950.     64           64           Future high-resolution adapters
  22951.  
  22952.  When you create a new icon or pointer, you can pick one of these three
  22953.  resolutions in which to edit the image. ICONEDIT saves the image in the .ICO
  22954.  or .PTR file in the editing dimension you choose. A 64-by-64 .ICO file is
  22955.  about eight times as large as a 32-by-16 .ICO file. When your program loads
  22956.  an icon or pointer into memory, the Presentation Manager adjusts the size of
  22957.  the image to match the video display adapter. For example, if the icon
  22958.  resource in your program's .EXE file is 32 by 32, the Presentation Manager
  22959.  eliminates every other row when displaying the icon on a Color Graphics
  22960.  Adapter and duplicates every row and column for a future high-resolution
  22961.  adapter.
  22962.  
  22963.  If you want to create icons and pointers that look satisfactory on every
  22964.  type of adapter and that take up the least amount of space in the .EXE file,
  22965.  use the 32-by-16 resolution. But be aware that they may appear grainy on an
  22966.  EGA or a high-resolution adapter. The other extreme is to create 64-by-64
  22967.  icons and pointers. These will look great on a future high-resolution
  22968.  adapter, but they may not do so well on the video adapters most commonly
  22969.  used today. Editing icons and pointers in a 32-by-32 resolution is a good
  22970.  compromise between these extremes, particularly considering that the Color
  22971.  Graphics Adapter is quickly becoming obsolete.
  22972.  
  22973.  The second major consideration when designing icons and pointers is color.
  22974.  Icons and pointers are made up of a pair of monochrome bitmaps. When you
  22975.  design an icon or pointer in ICONEDIT, you can color pixels in either black,
  22976.  white, "screen," or "inverse screen." The "screen" color is transparent.
  22977.  When the Presentation Manager displays the icon or pointer, whatever was
  22978.  originally behind it shows through. The "inverse screen" color inverts the
  22979.  background behind the image. A black background becomes white, white becomes
  22980.  black, and green becomes magenta.
  22981.  
  22982.  These four colors correspond to the bits in the two bitmaps that make up an
  22983.  icon or pointer, as shown in the following table:
  22984.  
  22985.     Bitmap 1        Bitmap 2        Resultant Color
  22986.     0               0               Black
  22987.     1               0               White
  22988.     0               1               Screen
  22989.     1               1               Inverse screen
  22990.  
  22991.  When the Presentation Manager displays the icon or pointer, it first draws
  22992.  Bitmap 2 on the screen using a bitwise AND operation (the raster operation
  22993.  ROP_SRCAND). The 1 bits in Bitmap 2 preserve the color bits on the screen;
  22994.  the 0 bits in Bitmap 2 set the screen bits to 0 (black). The Presentation
  22995.  Manager then draws Bitmap 1 on the screen using an exclusive OR operation
  22996.  (the raster operation ROP_SRCINVERT). The 0 bits in Bitmap 1 preserve the
  22997.  screen image, and the 1 bits invert the screen image. Using C notation for
  22998.  the logical operations, the display is altered by the following formula:
  22999.  
  23000.    Display = (Display & Bitmap2) ^ Bitmap1
  23001.  
  23002.  Because an icon or pointer can be displayed against a background of almost
  23003.  any color, a few simple rules apply in designing the images:
  23004.  
  23005.    ■  If the icon or pointer is mostly black, give it a white outline.
  23006.  
  23007.    ■  If the icon or pointer is mostly white, give it a black outline.
  23008.  
  23009.    ■  Use the "screen" color to make the icon or pointer nonrectangular (such
  23010.       as the common arrow pointer).
  23011.  
  23012.    ■  Use "inverse screen" to add a dash of inverted color when the icon or
  23013.       pointer is displayed against a color background.
  23014.  
  23015.  Referencing the File in the Resource Script
  23016.  
  23017.  The statements in your resource script that reference the icon and pointer
  23018.  files are very similar to the statement used for bitmaps. You reference an
  23019.  icon file as a resource with the following statement:
  23020.  
  23021.    POINTER idName sample.ico
  23022.  
  23023.  You reference a pointer file with the following statement:
  23024.  
  23025.    POINTER idName sample.ptr
  23026.  
  23027.  POINTER is a keyword recognized by RC.EXE.
  23028.  
  23029.  The use of the POINTER keyword for both icons and pointers may seem a little
  23030.  strange. As I mentioned earlier, icons and pointers are identical in
  23031.  structure and are in many ways interchangeable. Both icons and pointers are
  23032.  stored in a program's .EXE file with a resource type of RT_POINTER. If a
  23033.  different keyword (for example, ICON) were used to identify icons in a
  23034.  resource script, you might be tempted to use the same name ID for an icon
  23035.  resource and a pointer resource. The name IDs for any icons and pointers in
  23036.  a resource script must be unique.
  23037.  
  23038.  Six Steps to Adding an Icon to a Program
  23039.  
  23040.  By far the most common use of an icon is for a symbolic representation of a
  23041.  program when the window is minimized. You can add such an icon to a program
  23042.  through these six steps:
  23043.  
  23044.    1. Create an icon in ICONEDIT. Give the file the same name as your program
  23045.       but with a .ICO extension; for example, SAMPLE.ICO.
  23046.  
  23047.    2. Create a SAMPLE.RC resource script file containing a POINTER statement.
  23048.       For example:
  23049.  
  23050.    POINTER 555 sample.ico
  23051.  
  23052.       This statement defines a pointer resource (which is actually an icon)
  23053.       with a name ID of 555.
  23054.  
  23055.    3. Change your program's make file so that it looks like this:
  23056.  
  23057.    sample.obj : sample.c
  23058.         cl -c -G2sw -W2 sample.c
  23059.  
  23060.    sample.res : sample.rc sample.ico
  23061.         rc -r sample
  23062.  
  23063.    sample.exe : sample.obj sample.def
  23064.         link sample, /align:16, NUL, os2, sample
  23065.         rc sample.res
  23066.  
  23067.    sample.exe : sample.res
  23068.         rc sample.res
  23069.  
  23070.    4. Change the definition of flFrameFlags to include the FCF_ICON style:
  23071.  
  23072.    static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
  23073.                                FCF_SIZEBORDER    | FCF_MINMAX   |
  23074.                                FCF_SHELLPOSITION | FCF_TASKLIST |
  23075.                                FCF_ICON ;
  23076.  
  23077.    5. Change the call to WinCreateStdWindow so the second-to-last parameter
  23078.       is the name ID of the bitmap:
  23079.  
  23080.    hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  23081.                                    &flFrameFlags, szClientClass, NULL,
  23082.                                    0L, NULL, 555, &hwndClient) ;
  23083.  
  23084.    6. If you have a WinSendMsg call to send the frame window a WM_SETICON
  23085.       message, remove it.
  23086.  
  23087.  You're done. Remake the program.
  23088.  
  23089.  As you'll see in the next chapter, the second-to-last parameter to
  23090.  WinCreateStdWindow is actually the name ID of three different resources. If
  23091.  you include the frame creation flag FCF_MENU, the Presentation Manager uses
  23092.  that same name ID to load the program's menu. If you include FCF_ACCELTABLE,
  23093.  the same name ID references the program's keyboard accelerator table.
  23094.  
  23095.  You might want to use an identifier defined in a header file for the name ID
  23096.  of these three resources. In the programs in this chapter and the next two
  23097.  chapters, I use the identifier ID_RESOURCE for this purpose and define it to
  23098.  be equal to 1. In this case, a SAMPLE.H header file has the following
  23099.  statement:
  23100.  
  23101.    #define ID_RESOURCE 1
  23102.  
  23103.  The SAMPLE.RC resource script looks like this:
  23104.  
  23105.    #include "sample.h"
  23106.  
  23107.    POINTER ID_RESOURCE sample.ico
  23108.  
  23109.  The SAMPLE make file is changed so that it recompiles the source code file
  23110.  and resource script file if the header file changes:
  23111.  
  23112.    sample.obj : sample.c sample.h
  23113.         cl -c -G2sw -W2 sample.c
  23114.  
  23115.    sample.res : sample.rc sample.ico sample.h
  23116.         rc -r sample
  23117.  
  23118.    sample.exe : sample.obj sample.def
  23119.         link sample, /align:16, NUL, os2, sample
  23120.         rc sample.res
  23121.  
  23122.    sample.exe : sample.res
  23123.         rc sample.res
  23124.  
  23125.  The SAMPLE.C file includes the header file near the top of the program:
  23126.  
  23127.    #include "sample.h"
  23128.  
  23129.  and the second to last parameter of the WinCreateStdWindow function uses the
  23130.  defined name rather than a number:
  23131.  
  23132.    hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE
  23133.                                    &flFrameFlags, szClientClass, NULL,
  23134.                                    0L, NULL, ID_RESOURCE, &hwndClient ;
  23135.  
  23136.  Drawing Icons and Setting Pointers
  23137.  
  23138.  Besides using an icon as a symbolic representation of a program, you can
  23139.  also draw an icon on your client window. Because of the similarity between
  23140.  icons and pointers, the functions for loading and destroying icons are the
  23141.  same as those used for pointers.
  23142.  
  23143.  You first define a handle of type HPOINTER to store a handle to the icon:
  23144.  
  23145.    HPOINTER hIcon ;
  23146.  
  23147.  You then load the icon into memory using the WinLoadPointer function:
  23148.  
  23149.    hIcon = WinLoadPointer (HWND_DESKTOP, hmod, idName) ;
  23150.  
  23151.  The hmod parameter is NULL if the icon is stored in the program's .EXE file.
  23152.  You can then display the icon on a presentation space using the following
  23153.  function:
  23154.  
  23155.    WinDrawPointer (hps, x, y, hIcon, sFlags) ;
  23156.  
  23157.  where x and y are the coordinates of the presentation space corresponding to
  23158.  the lower-left corner of the icon. The sFlags parameter can be DP_NORMAL to
  23159.  draw the icon normally, DP_INVERTED to invert the icon, and DP_HALFTONED to
  23160.  draw only every other bit of the icon. You might want to use icons in this
  23161.  way in a menu that you create and manage. You use the inverted icon when the
  23162.  user selects an option and the "halftoned" icon when a menu option is
  23163.  disabled.
  23164.  
  23165.  Before your program terminates, you destroy the icon:
  23166.  
  23167.    WinDestroyPointer (hIcon) ;
  23168.  
  23169.  You can also use the WinLoadPointer and WinDestroyPointer functions with
  23170.  mouse pointers. If you create a customized mouse pointer and include it in
  23171.  your resource script, you can get a handle to the pointer in your program
  23172.  like this:
  23173.  
  23174.    hptr = WinLoadPointer (HWND_DESKTOP, hmod, idName) ;
  23175.  
  23176.  You'll probably do this during the WM_CREATE message. The hptr variable is
  23177.  defined as type HPOINTER.
  23178.  
  23179.  The easiest way for your program to use this new pointer is to set the
  23180.  pointer during the WM_MOUSEMOVE message:
  23181.  
  23182.    case WM_MOUSEMOVE:
  23183.         WinSetPointer (HWND_DESKTOP, hptr) ;
  23184.              [other program lines]
  23185.  
  23186.  
  23187.  You can also test the coordinates of the mouse pointer during the
  23188.  WM_MOUSEMOVE message and set a different pointer depending on where the
  23189.  pointer is located in the client area. If you divide your client area into
  23190.  several areas with the use of child windows, each child window might set its
  23191.  own pointer.
  23192.  
  23193.  During processing of the WM_DESTROY message, you destroy the pointer:
  23194.  
  23195.    WinDestroyPointer (hptr) ;
  23196.  
  23197.  The RESOURCE program, shown in Figure 12-10, contains an icon and a
  23198.  pointer resource. The icon is a square pattern that shows the four colors
  23199.  (black, white, "screen," and "inverse screen"). The program references the
  23200.  icon name ID in the WinCreateStdWindow call and while processing the
  23201.  WM_CREATE message. RESOURCE draws the icon on the four corners of its client
  23202.  window and shows what the "halftoned" and inverted icons look like. The
  23203.  customized pointer is displayed whenever the mouse is within RESOURCE's
  23204.  client window.
  23205.  
  23206.  Figure 12-10.  The RESOURCE program.
  23207.  
  23208.    The RESOURCE File
  23209.  
  23210.    #--------------------
  23211.    # RESOURCE make file
  23212.    #--------------------
  23213.  
  23214.    resource.obj : resource.c resource.h
  23215.         cl -c -G2sw -W3 resource.c
  23216.  
  23217.    resource.res : resource.rc resource.h resource.ico resource.ptr
  23218.         rc -r resource
  23219.  
  23220.    resource.exe : resource.obj resource.def
  23221.         link resource, /align:16, NUL, os2, resource
  23222.         rc resource.res
  23223.  
  23224.    resource.exe : resource.res
  23225.         rc resource.res
  23226.  
  23227.    The RESOURCE.C File
  23228.  
  23229.    /*-------------------------------------------------
  23230.       RESOURCE.C -- Uses an Icon and Pointer Resource
  23231.      -------------------------------------------------*/
  23232.  
  23233.    #define INCL_WIN
  23234.    #define INCL_GPI
  23235.    #include <os2.h>
  23236.    #include "resource.h"
  23237.  
  23238.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  23239.  
  23240.    int main (void)
  23241.         {
  23242.         static CHAR  szClientClass [] = "Resource" ;
  23243.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
  23244.                                     FCF_SIZEBORDER    | FCF_MINMAX   |
  23245.                                     FCF_SHELLPOSITION | FCF_TASKLIST |
  23246.                                     FCF_ICON ;
  23247.         HAB          hab ;
  23248.         HMQ          hmq ;
  23249.         HWND         hwndFrame, hwndClient ;
  23250.         QMSG         qmsg ;
  23251.  
  23252.         hab = WinInitialize (0) ;
  23253.         hmq = WinCreateMsgQueue (hab, 0) ;
  23254.  
  23255.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  23256.  
  23257.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  23258.                                         &flFrameFlags, szClientClass, NULL,
  23259.                                         0L, NULL, ID_RESOURCE, &hwndClient) ;
  23260.  
  23261.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  23262.              WinDispatchMsg (hab, &qmsg) ;
  23263.  
  23264.         WinDestroyWindow (hwndFrame) ;
  23265.         WinDestroyMsgQueue (hmq) ;
  23266.         WinTerminate (hab) ;
  23267.         return 0 ;
  23268.         }
  23269.  
  23270.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  23271.         {
  23272.         static HPOINTER hIcon, hptr ;
  23273.         static SHORT    cxClient, cyClient, cxIcon, cyIcon ;
  23274.         HPS             hps ;
  23275.         RECTL           rcl ;
  23276.  
  23277.         switch (msg)
  23278.              {
  23279.              case WM_CREATE:
  23280.                   hIcon = WinLoadPointer (HWND_DESKTOP, NULL, ID_RESOURCE) ;
  23281.                   hptr  = WinLoadPointer (HWND_DESKTOP, NULL, IDP_CIRCLE) ;
  23282.  
  23283.                   cxIcon = (SHORT) WinQuerySysValue (HWND_DESKTOP, SV_CXICON)
  23284.                   cyIcon = (SHORT) WinQuerySysValue (HWND_DESKTOP, SV_CYICON)
  23285.                   return 0 ;
  23286.  
  23287.              case WM_SIZE:
  23288.                   cxClient = SHORT1FROMMP (mp2) ;
  23289.                   cyClient = SHORT2FROMMP (mp2) ;
  23290.                   return 0 ;
  23291.  
  23292.              case WM_MOUSEMOVE:
  23293.                   WinSetPointer (HWND_DESKTOP, hptr) ;
  23294.                   return 1 ;
  23295.  
  23296.              case WM_PAINT:
  23297.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  23298.  
  23299.                   WinQueryWindowRect (hwnd, &rcl) ;
  23300.                   WinFillRect (hps, &rcl, CLR_CYAN) ;
  23301.  
  23302.                   WinDrawPointer (hps, 0, 0, hIcon, DP_NORMAL) ;
  23303.                   WinDrawPointer (hps, 0, cyClient - cyIcon, hIcon, DP_NORMAL)
  23304.                   WinDrawPointer (hps, cxClient - cyIcon, 0, hIcon, DP_NORMAL)
  23305.                   WinDrawPointer (hps, cxClient - cxIcon, cyClient - cyIcon,
  23306.                                        hIcon, DP_NORMAL) ;
  23307.  
  23308.                   WinDrawPointer (hps, cxClient / 3, cyClient / 2, hIcon,
  23309.                                                           DP_HALFTONED) ;
  23310.                   WinDrawPointer (hps, 2 * cxClient / 3, cyClient / 2, hIcon,
  23311.                                                           DP_INVERTED) ;
  23312.                   WinEndPaint (hps) ;
  23313.                   return 0 ;
  23314.  
  23315.              case WM_DESTROY:
  23316.                   WinDestroyPointer (hIcon) ;
  23317.                   WinDestroyPointer (hptr) ;
  23318.                   return 0 ;
  23319.              }
  23320.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  23321.         }
  23322.  
  23323.    The RESOURCE.H File
  23324.  
  23325.    /*------------------------
  23326.       RESOURCE.H header file
  23327.      ------------------------*/
  23328.  
  23329.    #define ID_RESOURCE 1
  23330.    #define IDP_CIRCLE  2
  23331.  
  23332.    The RESOURCE.RC File
  23333.  
  23334.    /*----------------------------------
  23335.       RESOURCE.RC resource script file
  23336.      ----------------------------------*/
  23337.  
  23338.    #include "resource.h"
  23339.  
  23340.    POINTER ID_RESOURCE resource.ico
  23341.    POINTER IDP_CIRCLE  resource.ptr
  23342.  
  23343.    The RESOURCE.ICO File
  23344.  
  23345.    The RESOURCE.PTR File
  23346.  
  23347.    The RESOURCE.DEF File
  23348.  
  23349.    ;-------------------------------------
  23350.    ; RESOURCE.DEF module definition file
  23351.    ;-------------------------------------
  23352.  
  23353.    NAME           RESOURCE  WINDOWAPI
  23354.  
  23355.    DESCRIPTION    'Icon and Pointer Resources (C) Charles Petzold, 1988'
  23356.    PROTMODE
  23357.    HEAPSIZE       1024
  23358.    STACKSIZE      8192
  23359.    EXPORTS        ClientWndProc
  23360.  
  23361.  
  23362.  
  23363.  The String Resource
  23364.  
  23365.  For bitmaps, icons, and pointers, there are some clear advantages to using
  23366.  resources rather than defining the images in your program's source code
  23367.  file. The ICONEDIT utility lets you draw the image and save it as a binary
  23368.  file──you don't have to worry about the format of the bits and bytes. So
  23369.  the next type of resource──the text string──may initially seem a little
  23370.  strange. Rather than put text strings in your C source code file, you can
  23371.  instead include them in the program's resource script. But why on earth
  23372.  would you want to do this?
  23373.  
  23374.  As you'll see in the next two chapters, a program's resource script also
  23375.  contains the program's menu and dialog box templates. If the resource script
  23376.  also contains all the text strings used by the program, converting the
  23377.  program to a foreign language requires that only the resource script (or
  23378.  files referenced by the resource script) be changed. The .C source code file
  23379.  doesn't even have to be recompiled. Of course, if your programs are intended
  23380.  only for yourself, a few friends, your corporation, or a domestic market,
  23381.  then using string resources provides no benefit, except perhaps──if the
  23382.  strings are handled properly──a slight saving in memory space when the
  23383.  program is running under the Presentation Manager.
  23384.  
  23385.  Defining and Loading String Resources
  23386.  
  23387.  You include strings in a resource script using the STRINGTABLE block:
  23388.  
  23389.    STRINGTABLE
  23390.         {
  23391.         idString1, "This little string went to market"
  23392.         idString2, "This little string stayed home"
  23393.              [other string definitions]
  23394.         }
  23395.  
  23396.  A resource script can have only one string table that contains all the
  23397.  program's strings. Each string is one line long, with a maximum of 255
  23398.  characters. You can use the keywords BEGIN and END rather than the curly
  23399.  brackets if you're nostalgic for Pascal syntax.
  23400.  
  23401.  In your program you load a particular string into a character array with the
  23402.  following function:
  23403.  
  23404.    WinLoadString (hab, hmod, idString, sBufferLen, achBuffer) ;
  23405.  
  23406.  As in the previous resource-loading functions, hmod is NULL if the strings
  23407.  are resources in the program's .EXE file. The function copies up to
  23408.  (sBufferLen - 1) characters into the character array addressed by achBuffer
  23409.  and appends a 0 character.
  23410.  
  23411.  To use the WinLoadString function, you need a character array in your
  23412.  program large enough to hold the string:
  23413.  
  23414.    CHAR achString [256] ;
  23415.         [other program lines]
  23416.    WinLoadString (hab, NULL, idString, sizeof achString, achString) ;
  23417.  
  23418.  Following this statement, achString contains the NULL-terminated string that
  23419.  was identified by idString in the resource script.
  23420.  
  23421.  You probably want to load strings only when you need them for display
  23422.  purposes. In that case, make the string arrays local variables in functions
  23423.  so that the space is freed up when the function ends.
  23424.  
  23425.  Using Strings for Error Messages
  23426.  
  23427.  Here's an example of how a program can use strings to display error messages
  23428.  in a message box. Suppose your program works with files and has three error
  23429.  messages: "File is not found," "File is too large to edit," and "File is
  23430.  read-only." You first define three identifiers in the program's header file:
  23431.  
  23432.    #define IDS_FILENOTFOUND 1
  23433.    #define IDS_FILETOOBIG   2
  23434.    #define IDS_FILEREADONLY 3
  23435.  
  23436.  The string table in the resource script looks like this:
  23437.  
  23438.    STRINGTABLE
  23439.         {
  23440.         IDS_FILENOTFOUND, "File %s not found."
  23441.         IDS_FILETOOBIG,   "File %s too large to edit."
  23442.         IDS_FILEREADONLY, "File %s is read-only."
  23443.         }
  23444.  
  23445.  In your program you define a function that displays one of these messages
  23446.  with a particular filename:
  23447.  
  23448.    VOID ErrorMessage (HWND hwnd, USHORT usErrorNum, CHAR * szFileName)
  23449.         {
  23450.         CHAR   achString [40] ;
  23451.         CHAR   achFormattedString [60] ;
  23452.  
  23453.         WinLoadString (hab, NULL, usErrorNum, sizeof achString, achString) ;
  23454.  
  23455.         sprintf (achFormattedString, achString, szFileName) ;
  23456.  
  23457.         WinMessageBox (HWND_DESKTOP, hwnd, achFormattedString,
  23458.                        NULL, 0, MB_OK | MB_ICONEXCLAMATION) ;
  23459.         }
  23460.  
  23461.  When the program needs to display the "File is not found" message, it calls
  23462.  the ErrorMessage function with the IDS_FILENOTFOUND identifier and the
  23463.  filename:
  23464.  
  23465.    ErrorMessage (hwnd, IDS_FILENOTFOUND, szFileName) ;
  23466.  
  23467.  String Resource Storage
  23468.  
  23469.  The string IDs in the STRINGTABLE block aren't treated the same way as the
  23470.  name IDs for bitmaps, icons, and cursors. Up to 16 strings are consolidated
  23471.  in the same resource segment in the program's .EXE file. All the strings
  23472.  with string IDs of 0 through 15 are in the same segment. The name ID for
  23473.  that segment is 1. The string IDs of 16 through 31 are in another segment
  23474.  with a name ID of 2.
  23475.  
  23476.  When you call WinLoadString, the Presentation Manager loads an entire
  23477.  resource segment into memory (containing up to 16 strings) and then copies
  23478.  the particular string you want into the array in your program's data
  23479.  segment. For this reason, you can conserve memory space if you assign string
  23480.  IDs in logical groups. For example, if one section of your program uses five
  23481.  strings and another section uses four strings, make the IDs of the first
  23482.  five strings 0 through 4 and the IDs of the other four strings 16 through
  23483.  19.
  23484.  
  23485.  
  23486.  Programmer-defined Resources
  23487.  
  23488.  The programmer-defined resource provides a way for you to attach arbitrary
  23489.  data to your program's .EXE file and load it into memory during program
  23490.  execution. Perhaps this data is in a binary form, and it's inconvenient to
  23491.  make it part of the program's source code file. Or perhaps you have a large
  23492.  text file (for example, a file that contains reams of "help" text) that your
  23493.  program must access. Make it a programmer-defined resource.
  23494.  
  23495.  The POEPOEM program, shown in Figure 12-11, shows how this is done. This
  23496.  program displays the text of Edgar Allan Poe's "Annabel Lee" in its client
  23497.  window. The text of the poem is a programmer-defined resource. The program's
  23498.  resource script also defines the text strings used in the program in a
  23499.  string table, as well as the program's icon.
  23500.  
  23501.  Figure 12-11.  The POEPOEM program.
  23502.  
  23503.    The POEPOEM File
  23504.  
  23505.    #-------------------
  23506.    # POEPOEM make file
  23507.    #-------------------
  23508.  
  23509.    poepoem.obj : poepoem.c poepoem.h
  23510.         cl -c -G2sw -W3 poepoem.c
  23511.  
  23512.    poepoem.res : poepoem.rc poepoem.ico poepoem.asc poepoem.h
  23513.         rc -r poepoem
  23514.  
  23515.    poepoem.exe : poepoem.obj poepoem.def
  23516.         link poepoem, /align:16, NUL, os2, poepoem
  23517.         rc poepoem.res
  23518.  
  23519.    poepoem.exe : poepoem.res
  23520.         rc poepoem.res
  23521.  
  23522.    The POEPOEM.C File
  23523.  
  23524.    /*--------------------------------------------------------
  23525.       POEPOEM.C -- Demonstrates Programmer-defined Resources
  23526.      --------------------------------------------------------*/
  23527.  
  23528.    #define INCL_WIN
  23529.    #define INCL_GPI
  23530.    #define INCL_DOS
  23531.    #include <os2.h>
  23532.  
  23533.    #include <stdlib.h>
  23534.    #include "poepoem.h"
  23535.  
  23536.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  23537.  
  23538.    int main (void)
  23539.         {
  23540.         static CHAR  szClientClass [10] ;
  23541.         static CHAR  szTitleBar [40] ;
  23542.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
  23543.                                     FCF_SIZEBORDER    | FCF_MINMAX   |
  23544.                                     FCF_SHELLPOSITION | FCF_TASKLIST |
  23545.                                     FCF_VERTSCROLL    | FCF_ICON ;
  23546.         HAB          hab ;
  23547.         HMQ          hmq ;
  23548.         HWND         hwndFrame, hwndClient ;
  23549.         QMSG         qmsg ;
  23550.  
  23551.         hab = WinInitialize (0) ;
  23552.         hmq = WinCreateMsgQueue (hab, 0) ;
  23553.  
  23554.         WinLoadString (hab, NULL, IDS_CLASS, sizeof szClientClass, szClientCla
  23555.         WinLoadString (hab, NULL, IDS_TITLE, sizeof szTitleBar,    szTitleBar)
  23556.  
  23557.         WinRegisterClass (hab, szClientClass, ClientWndProc, CS_SIZEREDRAW, 0)
  23558.  
  23559.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  23560.                                         &flFrameFlags, szClientClass, szTitleB
  23561.                                         0L, NULL, ID_RESOURCE, &hwndClient) ;
  23562.  
  23563.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  23564.              WinDispatchMsg (hab, &qmsg) ;
  23565.  
  23566.         WinDestroyWindow (hwndFrame) ;
  23567.         WinDestroyMsgQueue (hmq) ;
  23568.         WinTerminate (hab) ;
  23569.         return 0 ;
  23570.         }
  23571.  
  23572.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  23573.         {
  23574.         static HWND   hwndScroll ;
  23575.         static PCHAR  pResource ;
  23576.  
  23577.         static SEL    selResource ;
  23578.         static SHORT  cxClient, cyClient, cxChar, cyChar, cyDesc,
  23579.                       sScrollPos, sNumLines ;
  23580.         FONTMETRICS   fm ;
  23581.         HPS           hps ;
  23582.         PCHAR         pText ;
  23583.         POINTL        ptl ;
  23584.         SHORT         sLineLength, sLine ;
  23585.         ULONG         ulSegSize ;
  23586.  
  23587.         switch (msg)
  23588.              {
  23589.              case WM_CREATE:
  23590.  
  23591.                        /*-----------------------------------------
  23592.                           Load the resource, get size and address
  23593.                          -----------------------------------------*/
  23594.  
  23595.                   DosGetResource (NULL, IDT_TEXT, IDT_POEM, &selResource) ;
  23596.                   DosSizeSeg (selResource, &ulSegSize) ;
  23597.                   pResource = MAKEP (selResource, 0) ;
  23598.  
  23599.                        /*-----------------------------------------------
  23600.                           Determine how many text lines are in resource
  23601.                          -----------------------------------------------*/
  23602.  
  23603.                   pText = pResource ;
  23604.  
  23605.                   while (pText - pResource < (USHORT) ulSegSize)
  23606.                        {
  23607.                        if (*pText == '\0' || *pText == '\x1A')
  23608.                             break ;
  23609.  
  23610.                        if (*pText == '\r')
  23611.                             sNumLines ++ ;
  23612.  
  23613.                        pText++ ;
  23614.                        }
  23615.  
  23616.                        /*------------------------------------------
  23617.                           Initialize scroll bar range and position
  23618.                          ------------------------------------------*/
  23619.  
  23620.                   hwndScroll = WinWindowFromID (
  23621.                                       WinQueryWindow (hwnd, QW_PARENT, FALSE),
  23622.                                       FID_VERTSCROLL) ;
  23623.  
  23624.                   WinSendMsg (hwndScroll, SBM_SETSCROLLBAR,
  23625.                                           MPFROM2SHORT (sScrollPos, 0),
  23626.                                           MPFROM2SHORT (0, sNumLines - 1)) ;
  23627.  
  23628.                        /*----------------------
  23629.                           Query character size
  23630.                          ----------------------*/
  23631.  
  23632.                   hps = WinGetPS (hwnd) ;
  23633.  
  23634.                   GpiQueryFontMetrics (hps, (LONG) sizeof fm, &fm) ;
  23635.                   cxChar = (SHORT) fm.lAveCharWidth ;
  23636.                   cyChar = (SHORT) fm.lMaxBaselineExt ;
  23637.                   cyDesc = (SHORT) fm.lMaxDescender ;
  23638.  
  23639.                   WinReleasePS (hps) ;
  23640.                   return 0 ;
  23641.  
  23642.              case WM_SIZE:
  23643.                   cxClient = SHORT1FROMMP (mp2) ;
  23644.                   cyClient = SHORT2FROMMP (mp2) ;
  23645.                   return 0 ;
  23646.  
  23647.              case WM_CHAR:
  23648.                   return WinSendMsg (hwndScroll, msg, mp1, mp2) ;
  23649.  
  23650.              case WM_VSCROLL:
  23651.                   switch (SHORT2FROMMP (mp2))
  23652.                        {
  23653.                        case SB_LINEUP:
  23654.                             sScrollPos -= 1 ;
  23655.                             break ;
  23656.  
  23657.                        case SB_LINEDOWN:
  23658.                             sScrollPos += 1 ;
  23659.                             break ;
  23660.  
  23661.                        case SB_PAGEUP:
  23662.                             sScrollPos -= cyClient / cyChar ;
  23663.                             break ;
  23664.  
  23665.                        case SB_PAGEDOWN:
  23666.                             sScrollPos += cyClient / cyChar ;
  23667.                             break ;
  23668.  
  23669.                        case SB_SLIDERPOSITION:
  23670.                             sScrollPos = SHORT1FROMMP (mp2) ;
  23671.                             break ;
  23672.                        }
  23673.                   sScrollPos = max (0, min (sScrollPos, sNumLines - 1)) ;
  23674.  
  23675.                   if (sScrollPos != (SHORT) WinSendMsg (hwndScroll,
  23676.                                                         SBM_QUERYPOS, 0L, 0L))
  23677.                        {
  23678.                        WinSendMsg (hwndScroll, SBM_SETPOS,
  23679.                                    MPFROM2SHORT (sScrollPos, 0), NULL) ;
  23680.                        WinInvalidateRect (hwnd, NULL, FALSE) ;
  23681.                        }
  23682.                   return 0 ;
  23683.  
  23684.              case WM_PAINT:
  23685.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  23686.                   GpiErase (hps) ;
  23687.  
  23688.                   pText = pResource ;
  23689.  
  23690.                   for (sLine = 0 ; sLine < sNumLines ; sLine++)
  23691.                        {
  23692.                        sLineLength = 0 ;
  23693.  
  23694.                        while (pText [sLineLength] != '\r')
  23695.                             sLineLength ++ ;
  23696.  
  23697.                        ptl.x = cxChar ;
  23698.                        ptl.y = cyClient - cyChar * (sLine + 1 - sScrollPos)
  23699.                                         + cyDesc ;
  23700.  
  23701.                        GpiCharStringAt (hps, &ptl, (LONG) sLineLength, pText)
  23702.  
  23703.                        pText += sLineLength + 2 ;
  23704.                        }
  23705.                   WinEndPaint (hps) ;
  23706.                   return 0 ;
  23707.  
  23708.              case WM_DESTROY:
  23709.                   DosFreeSeg (selResource) ;
  23710.                   return 0 ;
  23711.              }
  23712.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  23713.         }
  23714.  
  23715.    The POEPOEM.H File
  23716.  
  23717.    /*-----------------------
  23718.       POEPOEM.H header file
  23719.      -----------------------*/
  23720.  
  23721.    #define ID_RESOURCE      1
  23722.  
  23723.    #define IDT_TEXT      1024
  23724.    #define IDT_POEM         1
  23725.  
  23726.    #define IDS_CLASS        0
  23727.    #define IDS_TITLE        1
  23728.  
  23729.    The POEPOEM.RC File
  23730.  
  23731.    /*---------------------------------
  23732.       POEPOEM.RC resource script file
  23733.      ---------------------------------*/
  23734.  
  23735.    #include "poepoem.h"
  23736.  
  23737.    POINTER ID_RESOURCE poepoem.ico
  23738.  
  23739.    RESOURCE IDT_TEXT IDT_POEM poepoem.asc
  23740.  
  23741.    STRINGTABLE
  23742.         {
  23743.         IDS_CLASS, "PoePoem"
  23744.         IDS_TITLE, " - ""Annabel Lee"" by Edgar Allan Poe"
  23745.         }
  23746.  
  23747.    The POEPOEM.ICO File
  23748.  
  23749.    The POEPOEM.ASC File
  23750.  
  23751.    It was many and many a year ago,
  23752.         In a kingdom by the sea,
  23753.    That a maiden there lived whom you may know
  23754.         By the name of Annabel Lee;
  23755.    And this maiden she lived with no other thought
  23756.         Than to love and be loved by me.
  23757.  
  23758.    I was a child and she was a child
  23759.         In this kingdom by the sea,
  23760.    But we loved with a love that was more than love --
  23761.         I and my Annabel Lee --
  23762.    With a love that the winged seraphs of Heaven
  23763.         Coveted her and me.
  23764.  
  23765.    And this was the reason that, long ago,
  23766.         In this kingdom by the sea,
  23767.    A wind blew out of a cloud, chilling
  23768.         My beautiful Annabel Lee;
  23769.    So that her highborn kinsmen came
  23770.         And bore her away from me,
  23771.    To shut her up in a sepulchre
  23772.         In this kingdom by the sea.
  23773.  
  23774.    The angels, not half so happy in Heaven,
  23775.         Went envying her and me --
  23776.    Yes! that was the reason (as all men know,
  23777.         In this kingdom by the sea)
  23778.    That the wind came out of the cloud by night,
  23779.         Chilling and killing my Annabel Lee.
  23780.  
  23781.    But our love it was stronger by far than the love
  23782.         Of those who were older than we --
  23783.         Of many far wiser than we --
  23784.    And neither the angels in Heaven above
  23785.         Nor the demons down under the sea
  23786.    Can ever dissever my soul from the soul
  23787.         Of the beautiful Annabel Lee:
  23788.  
  23789.    For the moon never beams, without bringing me dreams
  23790.         Of the beautiful Annabel Lee;
  23791.    And the stars never rise, but I feel the bright eyes
  23792.         Of the beautiful Annabel Lee:
  23793.    And so, all the night-tide, I lie down by the side
  23794.  
  23795.    Of my darling -- my darling -- my life and my bride,
  23796.         In her sepulchre there by the sea --
  23797.         In her tomb by the sounding sea.
  23798.  
  23799.                                           [May, 1849]
  23800.  
  23801.    The POEPOEM.DEF File
  23802.  
  23803.    ;------------------------------------
  23804.    ; POEPOEM.DEF module definition file
  23805.    ;------------------------------------
  23806.  
  23807.    NAME           POEPOEM   WINDOWAPI
  23808.  
  23809.    DESCRIPTION    'Programmer-defined Resource (C) Charles Petzold, 1988'
  23810.    PROTMODE
  23811.    HEAPSIZE       1024
  23812.    STACKSIZE      8192
  23813.    EXPORTS        ClientWndProc
  23814.  
  23815.  
  23816.  The POEPOEM.ASC file contains the text of the poem. This text is made a
  23817.  programmer-defined resource by referencing it in the resource script with
  23818.  this statement:
  23819.  
  23820.    RESOURCE IDT_TEXT IDT_POEM poepoem.asc
  23821.  
  23822.  The IDT_TEXT and IDT_POEM identifiers are defined in POEPOEM.H:
  23823.  
  23824.    #define IDT_TEXT      1024
  23825.    #define IDT_POEM         1
  23826.  
  23827.  IDT_TEXT is the resource type ID. Programmer-defined resources must have
  23828.  type IDs of 256 or greater. IDT_POEM is the name ID.
  23829.  
  23830.  During processing of the WM_CREATE message, POEPOEM obtains a segment
  23831.  selector to the resource by calling the OS/2 DosGetResource function:
  23832.  
  23833.    DosGetResource (NULL, IDT_TEXT, IDT_POEM, &selResource) ;
  23834.  
  23835.  When OS/2 loads the resource into memory, it allocates a memory block and
  23836.  returns the selector to the memory block in the selResource variable, which
  23837.  is defined as type SEL. POEPOEM converts this selector to a far pointer
  23838.  using the MAKEP macro:
  23839.  
  23840.    pResource = MAKEP (selResource, 0) ;
  23841.  
  23842.  A program can also use other OS/2 functions with this memory block, such as
  23843.  DosSizeSeg to find the size of the segment:
  23844.  
  23845.    DosSizeSeg (selResource, &ulSegSize) ;
  23846.  
  23847.  The only action that a program can't take is to write on this memory block.
  23848.  Resources loaded into memory using DosGetResource are always read-only.
  23849.  However, a program can allocate another memory block using DosAllocSeg and
  23850.  copy the data for later modification.
  23851.  
  23852.  During the WM_CREATE message, POEPOEM determines the number of lines of text
  23853.  in the poem and sets the range of a scroll bar accordingly. All WM_CHAR
  23854.  messages to the client window are sent to the scroll bar to give the program
  23855.  a complete keyboard interface. POEPOEM displays the text during the WM_PAINT
  23856.  message. The only assumption it makes is that each line of text is
  23857.  terminated by a carriage return and a linefeed.
  23858.  
  23859.  During the WM_DESTROY message, POEPOEM frees the memory block:
  23860.  
  23861.    DosFreeSeg (selResource) ;
  23862.  
  23863.  You'll notice that POEPOEM.C itself contains no displayable text. The text
  23864.  used in the title bar is defined in the resource script. We've thus made it
  23865.  easier for translators to convert the program to a foreign-language version.
  23866.  Of course, they would also need to translate the text of "Annabel Lee,"
  23867.  which is a far more challenging job.
  23868.  
  23869.  
  23870.  Chapter 13  Menus and Keyboard Accelerators
  23871.  ───────────────────────────────────────────────────────────────────────────
  23872.  
  23873.  
  23874.  The menu is an important part of the consistent user interface in
  23875.  Presentation Manager programs. Users learn a new program more quickly if the
  23876.  program has a menu that works like the menus in other Presentation Manager
  23877.  programs.
  23878.  
  23879.  In one sense, putting a menu in a Presentation Manager program is fairly
  23880.  easy. You define the menu template in a resource script file, and you
  23881.  process WM_COMMAND messages from the menu in your client window procedure.
  23882.  The Presentation Manager takes care of all the keyboard and mouse processing
  23883.  involved with the menu. However, menus are also one of the more complex
  23884.  aspects of the Presentation Manager's windowing environment because they can
  23885.  be extensively tailored to the program's needs.
  23886.  
  23887.  Let's nail down some terminology first. A "menu" is a control window created
  23888.  by WinCreateStdWindow as part of the standard window. A menu contains
  23889.  several items, each of which can be selected using either the mouse or the
  23890.  keyboard. The horizontal menu that appears below the window's title bar is
  23891.  called the program's "main menu" or "top-level menu" or the "action bar."
  23892.  I've generally used the term top-level menu for this.
  23893.  
  23894.  Some menu items invoke another menu called a "popup menu" or a "drop-down
  23895.  menu" or a "pull-down menu" or a "submenu." I'll use the term submenu
  23896.  because that's the word used in several identifiers defined in the
  23897.  Presentation Manager header files. From the perspective of a program, each
  23898.  submenu is a separate window. Thus, when you create a top-level menu that
  23899.  invokes three submenus, you're actually creating four menu-control windows.
  23900.  
  23901.  A Presentation Manager program also usually contains three other
  23902.  menu-control windows. One is the system menu, which contains one item──a
  23903.  little bitmapped picture to the left of the title bar. The system menu
  23904.  invokes a submenu. The minimize/maximize icon to the right of the title bar
  23905.  is also a menu. It contains two items, both of which are bitmaps.
  23906.  
  23907.  Menu items can be "enabled" or "disabled." A disabled menu item appears in
  23908.  gray text. Although the user can click on a disabled menu item or use the
  23909.  keyboard to move a reverse-video bar to the menu item, the menu beeps and
  23910.  does not send a WM_COMMAND message to the program.
  23911.  
  23912.  
  23913.  Conventional Menus
  23914.  
  23915.  The CONVMENU program, shown in Figure 13-1, contains a conventional menu
  23916.  and demonstrates some sample menu processing. This program and the
  23917.  discussion that follows cover just about everything you'll need to know to
  23918.  implement a menu in most of your programs. The CONVMENU program also
  23919.  contains a keyboard accelerator table. Keyboard accelerators are key
  23920.  combinations that usually duplicate some menu items.
  23921.  
  23922.  Figure 13-1.  The CONVMENU program.
  23923.  
  23924.    The CONVMENU File
  23925.  
  23926.    #--------------------
  23927.    # CONVMENU make file
  23928.    #--------------------
  23929.  
  23930.    convmenu.obj : convmenu.c convmenu.h
  23931.         cl -c -G2sw -W3 convmenu.c
  23932.  
  23933.    convmenu.res : convmenu.rc convmenu.h
  23934.         rc -r convmenu
  23935.  
  23936.    convmenu.exe : convmenu.obj convmenu.def
  23937.         link convmenu, /align:16, NUL, os2, convmenu
  23938.         rc convmenu.res
  23939.  
  23940.    convmenu.exe : convmenu.res
  23941.         rc convmenu.res
  23942.  
  23943.    The CONVMENU.C File
  23944.  
  23945.    /*-------------------------------------
  23946.       CONVMENU.C -- Conventional Menu Use
  23947.      -------------------------------------*/
  23948.  
  23949.    #define INCL_WIN
  23950.    #define INCL_GPI
  23951.    #include <os2.h>
  23952.    #include "convmenu.h"
  23953.  
  23954.    #define ID_TIMER    1
  23955.  
  23956.    MRESULT EXPENTRY ClientWndProc (HWND, USHORT, MPARAM, MPARAM) ;
  23957.  
  23958.    CHAR szClientClass[] = "ConvMenu" ;
  23959.    HAB  hab ;
  23960.  
  23961.    int main (void)
  23962.         {
  23963.         static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU  |
  23964.                                     FCF_SIZEBORDER    | FCF_MINMAX   |
  23965.                                     FCF_SHELLPOSITION | FCF_TASKLIST |
  23966.                                     FCF_MENU          | FCF_ACCELTABLE ;
  23967.         HMQ          hmq ;
  23968.         HWND         hwndFrame, hwndClient ;
  23969.         QMSG         qmsg ;
  23970.  
  23971.         hab = WinInitialize (0) ;
  23972.         hmq = WinCreateMsgQueue (hab, 0) ;
  23973.  
  23974.         WinRegisterClass (hab, szClientClass, ClientWndProc, 0L, 0) ;
  23975.  
  23976.         hwndFrame = WinCreateStdWindow (HWND_DESKTOP, WS_VISIBLE,
  23977.                                         &flFrameFlags, szClientClass, NULL,
  23978.                                         0L, NULL, ID_RESOURCE, &hwndClient) ;
  23979.  
  23980.         WinSendMsg (hwndFrame, WM_SETICON,
  23981.                     WinQuerySysPointer (HWND_DESKTOP, SPTR_APPICON, FALSE),
  23982.                     NULL) ;
  23983.  
  23984.         while (TRUE)
  23985.              {
  23986.              while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  23987.                   WinDispatchMsg (hab, &qmsg) ;
  23988.              if (MBID_OK == WinMessageBox (HWND_DESKTOP, hwndClient,
  23989.                                            "Really want to end program?",
  23990.                                            szClientClass, 0,
  23991.                                            MB_OKCANCEL | MB_ICONQUESTION))
  23992.                   break ;
  23993.  
  23994.              WinCancelShutdown (hmq, FALSE) ;
  23995.              }
  23996.  
  23997.         WinDestroyWindow (hwndFrame) ;
  23998.         WinDestroyMsgQueue (hmq) ;
  23999.         WinTerminate (hab) ;
  24000.         return 0 ;
  24001.         }
  24002.  
  24003.    MRESULT EXPENTRY ClientWndProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM m
  24004.         {
  24005.         static BOOL  fTimerGoing = FALSE ;
  24006.         static COLOR colBackground [] = {
  24007.                                         0xFFFFFFL, 0xC0C0C0L, 0x808080L,
  24008.                                         0x404040L, 0x000000L
  24009.                                         } ;
  24010.         static HWND  hwndMenu ;
  24011.         static SHORT sCurrentBackground = IDM_WHITE ;
  24012.         HPS          hps ;
  24013.         RECTL        rcl ;
  24014.  
  24015.         switch (msg)
  24016.              {
  24017.              case WM_CREATE:
  24018.                   hwndMenu = WinWindowFromID (
  24019.                                  WinQueryWindow (hwnd, QW_PARENT, FALSE),
  24020.                                  FID_MENU) ;
  24021.                   return 0 ;
  24022.  
  24023.              case WM_INITMENU:
  24024.                   switch (SHORT1FROMMP (mp1))
  24025.                        {
  24026.                        case IDM_TIMER:
  24027.                             WinSendMsg (hwndMenu, MM_SETITEMATTR,
  24028.                                       MPFROM2SHORT (IDM_START, TRUE),
  24029.                                       MPFROM2SHORT (MIA_DISABLED,
  24030.                                                 !fTimerGoing &&
  24031.  
  24032.                                  WinQuerySysValue (HWND_DESKTOP, SV_CTIMERS) ?
  24033.                                                 0 : MIA_DISABLED)) ;
  24034.  
  24035.                             WinSendMsg (hwndMenu, MM_SETITEMATTR,
  24036.                                       MPFROM2SHORT (IDM_STOP, TRUE),
  24037.                                       MPFROM2SHORT (MIA_DISABLED,
  24038.                                            fTimerGoing ? 0 : MIA_DISABLED)) ;
  24039.                             return 0 ;
  24040.                        }
  24041.                   break ;
  24042.  
  24043.              case WM_COMMAND:
  24044.                   switch (COMMANDMSG(&msg)->cmd)
  24045.                        {
  24046.                        case IDM_NEW:
  24047.                             WinMessageBox (HWND_DESKTOP, hwnd,
  24048.                                       "Bogus \"New\" Dialog",
  24049.                                       szClientClass, 0, MB_OK | MB_ICONASTERIS
  24050.                             return 0 ;
  24051.  
  24052.                        case IDM_OPEN:
  24053.                             WinMessageBox (HWND_DESKTOP, hwnd,
  24054.                                       "Bogus \"Open\" Dialog",
  24055.                                       szClientClass, 0, MB_OK | MB_ICONASTERIS
  24056.                             return 0 ;
  24057.  
  24058.                        case IDM_SAVE:
  24059.                             WinMessageBox (HWND_DESKTOP, hwnd,
  24060.                                       "Bogus \"Save\" Dialog",
  24061.                                       szClientClass, 0, MB_OK | MB_ICONASTERIS
  24062.                             return 0 ;
  24063.  
  24064.                        case IDM_SAVEAS:
  24065.                             WinMessageBox (HWND_DESKTOP, hwnd,
  24066.                                       "Bogus \"Save As\" Dialog",
  24067.                                       szClientClass, 0, MB_OK | MB_ICONASTERIS
  24068.                             return 0 ;
  24069.  
  24070.                        case IDM_EXIT:
  24071.                             WinSendMsg (hwnd, WM_CLOSE, 0L, 0L) ;
  24072.                             return 0 ;
  24073.                        case IDM_ABOUT:
  24074.                             WinMessageBox (HWND_DESKTOP, hwnd,
  24075.                                       "Bogus \"About\" Dialog",
  24076.                                       szClientClass, 0, MB_OK | MB_ICONASTERIS
  24077.                             return 0 ;
  24078.  
  24079.                        case IDM_START:
  24080.                             if (WinStartTimer (hab, hwnd, ID_TIMER, 1000))
  24081.                                  fTimerGoing = TRUE ;
  24082.                             else
  24083.                                  WinMessageBox (HWND_DESKTOP, hwnd,
  24084.                                       "Too many clocks or timers",
  24085.                                       szClientClass, 0,
  24086.                                       MB_OK | MB_ICONEXCLAMATION) ;
  24087.                             return 0 ;
  24088.  
  24089.                        case IDM_STOP:
  24090.                             WinStopTimer (hab, hwnd, ID_TIMER) ;
  24091.                             fTimerGoing = FALSE ;
  24092.                             return 0 ;
  24093.  
  24094.                        case IDM_WHITE:
  24095.                        case IDM_LTGRAY:
  24096.                        case IDM_GRAY:
  24097.                        case IDM_DKGRAY:
  24098.                        case IDM_BLACK:
  24099.                             WinSendMsg (hwndMenu, MM_SETITEMATTR,
  24100.                                         MPFROM2SHORT (sCurrentBackground, TRUE
  24101.                                         MPFROM2SHORT (MIA_CHECKED, 0)) ;
  24102.  
  24103.                             sCurrentBackground = COMMANDMSG(&msg)->cmd ;
  24104.  
  24105.                             WinSendMsg (hwndMenu, MM_SETITEMATTR,
  24106.                                         MPFROM2SHORT (sCurrentBackground, TRUE
  24107.                                         MPFROM2SHORT (MIA_CHECKED, MIA_CHECKED
  24108.  
  24109.                             WinInvalidateRect (hwnd, NULL, FALSE) ;
  24110.                             return 0 ;
  24111.                        }
  24112.                   break ;
  24113.              case WM_HELP:
  24114.                   WinMessageBox (HWND_DESKTOP, hwnd,
  24115.                                  "Help not yet implemented",
  24116.                                  szClientClass, 0, MB_OK | MB_ICONEXCLAMATION)
  24117.                   return 0 ;
  24118.  
  24119.              case WM_TIMER:
  24120.                   WinAlarm (HWND_DESKTOP, WA_NOTE) ;
  24121.                   return 0 ;
  24122.  
  24123.              case WM_PAINT:
  24124.                   hps = WinBeginPaint (hwnd, NULL, NULL) ;
  24125.                   GpiSavePS (hps) ;
  24126.  
  24127.                   GpiCreateLogColorTable (hps, 0L, LCOLF_RGB, 0L, 0L, NULL) ;
  24128.  
  24129.                   WinQueryWindowRect (hwnd, &rcl) ;
  24130.  
  24131.                   WinFillRect (hps, &rcl,
  24132.                                colBackground [sCurrentBackground - IDM_WHITE])
  24133.  
  24134.                   GpiRestorePS (hps, -1L) ;
  24135.                   WinEndPaint (hps) ;
  24136.                   return 0 ;
  24137.  
  24138.              case WM_DESTROY:
  24139.                   if (fTimerGoing)
  24140.                        {
  24141.                        WinStopTimer (hab, hwnd, ID_TIMER) ;
  24142.                        fTimerGoing = FALSE ;
  24143.                        }
  24144.                   return 0 ;
  24145.              }
  24146.         return WinDefWindowProc (hwnd, msg, mp1, mp2) ;
  24147.         }
  24148.  
  24149.    The CONVMENU.H File
  24150.  
  24151.    /*------------------------
  24152.       CONVMENU.H header file
  24153.      ------------------------*/
  24154.  
  24155.    #define ID_RESOURCE      1
  24156.  
  24157.    #define IDM_FILE         1    // Top-level items
  24158.    #define IDM_TIMER        2
  24159.    #define IDM_BACKGROUND   3
  24160.    #define IDM_TOPEXIT      4
  24161.    #define IDM_HELP         5
  24162.  
  24163.    #define IDM_NEW          10   // "File" submenu
  24164.    #define IDM_OPEN         11
  24165.    #define IDM_SAVE         12
  24166.    #define IDM_SAVEAS       13
  24167.    #define IDM_ABOUT        14
  24168.  
  24169.    #define IDM_START        20   // "Timer" submenu
  24170.    #define IDM_STOP         21
  24171.  
  24172.    #define IDM_WHITE        30   // "Background" submenu
  24173.    #define IDM_LTGRAY       31
  24174.    #define IDM_GRAY         32        // Program logic assumes these
  24175.    #define IDM_DKGRAY       33        // five numbers are consecutive
  24176.    #define IDM_BLACK        34
  24177.  
  24178.    #define IDM_EXIT         40   // "Exit" submenu
  24179.    #define IDM_RESUME       41
  24180.  
  24181.    The CONVMENU.RC File
  24182.  
  24183.    /*----------------------------------
  24184.       CONVMENU.RC resource script file
  24185.      ----------------------------------*/
  24186.  
  24187.    #include <os2.h>
  24188.    #include "convmenu.h"
  24189.  
  24190.    MENU ID_RESOURCE
  24191.         {
  24192.         SUBMENU "~File",              IDM_FILE
  24193.              {
  24194.              MENUITEM "~New",                   IDM_NEW
  24195.              MENUITEM "~Open...",               IDM_OPEN
  24196.              MENUITEM "~Save\tShift+F3",        IDM_SAVE
  24197.              MENUITEM "Save ~As...",            IDM_SAVEAS
  24198.              MENUITEM SEPARATOR
  24199.              MENUITEM "A~bout ConvMenu...",     IDM_ABOUT
  24200.              }
  24201.         SUBMENU "~Timer",             IDM_TIMER
  24202.              {
  24203.              MENUITEM "~Start",                 IDM_START
  24204.              MENUITEM "S~top",                  IDM_STOP,,  MIA_DISABLED
  24205.              }
  24206.         SUBMENU "~Background",        IDM_BACKGROUND
  24207.              {
  24208.              MENUITEM "~White\tCtrl+W",         IDM_WHITE,, MIA_CHECKED
  24209.              MENUITEM "~Light Gray\tCtrl+L",    IDM_LTGRAY
  24210.              MENUITEM "~Gray\tCtrl+G",          IDM_GRAY
  24211.              MENUITEM "~Dark Gray\tCtrl+D",     IDM_DKGRAY
  24212.              MENUITEM "~Black\tCtrl+B",         IDM_BLACK
  24213.              }
  24214.         SUBMENU "E~xit",              IDM_TOPEXIT
  24215.              {
  24216.              MENUITEM "E~xit ConvMenu...\tF3",  IDM_EXIT
  24217.              MENUITEM "~Resume ConvMenu",       IDM_RESUME
  24218.              }
  24219.         MENUITEM "F1=Help",           IDM_HELP, MIS_HELP | MIS_BUTTONSEPARATOR
  24220.         }
  24221.  
  24222.    ACCELTABLE ID_RESOURCE
  24223.         {
  24224.         VK_F3, IDM_SAVE,   VIRTUALKEY, SHIFT
  24225.         VK_F3, IDM_EXIT,   VIRTUALKEY
  24226.         "^W",  IDM_WHITE
  24227.         "^L",  IDM_LTGRAY
  24228.         "^G",  IDM_GRAY
  24229.         "^D",  IDM_DKGRAY
  24230.         "^B",  IDM_BLACK
  24231.         }
  24232.  
  24233.    The CONVMENU.DEF File
  24234.  
  24235.    ;-------------------------------------
  24236.    ; CONVMENU.DEF module definition file
  24237.    ;-------------------------------------
  24238.  
  24239.    NAME           CONVMENU  WINDOWAPI
  24240.  
  24241.    DESCRIPTION    'Conventional Menu Demo (C) Charles Petzold, 1988'
  24242.    PROTMODE
  24243.    HEAPSIZE       1024
  24244.    STACKSIZE      8192
  24245.    EXPORTS        ClientWndProc
  24246.  
  24247.  
  24248.  The File submenu in CONVMENU contains standard options that don't do
  24249.  anything in this program except display some message boxes. (In the next
  24250.  chapter you'll see how to invoke dialog boxes from menu items.) The Timer
  24251.  menu starts and stops the timer. The timer causes the program to beep once a
  24252.  second. When the timer is active, the Start option is disabled; when the
  24253.  timer is inactive, the Stop option is disabled. The Background menu changes
  24254.  the background color of the client window. This menu uses check marks to
  24255.  indicate the current color.
  24256.  
  24257.  Defining the Menu
  24258.  
  24259.  You define a menu template in a resource script file. The menu template
  24260.  begins with the MENU statement, which indicates the resource name ID of the
  24261.  menu. The menu in CONVMENU.RC has a resource name ID of ID_RESOURCE, which
  24262.  is defined in CONVMENU.H. The actual definition of the menu is enclosed
  24263.  within a pair of curly brackets:
  24264.  
  24265.    MENU ID_RESOURCE
  24266.         {
  24267.              [menu definition]
  24268.         }
  24269.  
  24270.  If you want, you can use the BEGIN and END keywords rather than the curly
  24271.  brackets.
  24272.  
  24273.  Between the curly brackets, you specify the items on the top-level menu by
  24274.  one or more MENUITEM or SUBMENU statements. The SUBMENU statement indicates
  24275.  a menu item that invokes a submenu, and the MENUITEM statement indicates a
  24276.  menu item that doesn't:
  24277.  
  24278.    MENU ID_RESOURCE
  24279.         {
  24280.         SUBMENU "~File",         IDM_FILE
  24281.              {
  24282.                   [definition of submenu]
  24283.              }
  24284.         SUBMENU "~Timer",        IDM_TIMER
  24285.              {
  24286.                   [definition of submenu]
  24287.              }
  24288.         SUBMENU "~Background",   IDM_BACKGROUND
  24289.              {
  24290.                   [definition of submenu]
  24291.              }
  24292.         SUBMENU "E~xit",         IDM_TOPEXIT
  24293.              {
  24294.                   [definition of submenu]
  24295.              }
  24296.         MENUITEM "F1=Help",      IDM_HELP, MIS_HELP | MIS_BUTTONSEPARATOR
  24297.         }
  24298.  
  24299.  Thus the top-level menu in CONVMENU contains the options "File," "Timer,"
  24300.  "Background," and "F1=Help."
  24301.  
  24302.  The syntax of the MENUITEM and SUBMENU statements is the same. Each
  24303.  statement contains a text string and a menu item ID followed by optional
  24304.  style and attribute identifiers:
  24305.  
  24306.    MENUITEM "Text", idMenuItem [,[style flags][, attribute flags]]
  24307.    SUBMENU  "Text", idMenuItem [,[style flags][, attribute flags]]
  24308.  
  24309.  The text string is the text that appears in the menu. A tilde (~) character
  24310.  causes the letter that follows the tilde to be underlined when the text is
  24311.  displayed. A user can type that letter in combination with the Alt key to
  24312.  select the menu item from the keyboard. The underlined letters within the
  24313.  top-level menu and each submenu should be unique. It's recommended that you
  24314.  use the first letter, the first consonant, or a subsequent consonant.
  24315.  
  24316.  The menu item ID is a 16-bit number that the Presentation Manager uses to
  24317.  identify the menu item in messages from the menu to your client window. You
  24318.  also use the menu item ID to send messages to the menu. The menu definition
  24319.  in CONVMENU.RC uses identifiers that are defined in CONVMENU.H and begin
  24320.  with the letters IDM ("ID for a menu item").
  24321.  
  24322.  The optional styles and attributes are one or more identifiers beginning
  24323.  with the letters MIS ("menu item style") or MIA ("menu item attribute").
  24324.  I'll describe these styles and attributes shortly.
  24325.  
  24326.  The SUBMENU statement indicates a menu item that invokes a submenu. The
  24327.  submenu is defined by one or more MENUITEM statements within a pair of curly
  24328.  brackets that follow the SUBMENU statement, like this:
  24329.  
  24330.    SUBMENU "~Timer",        IDM_TIMER
  24331.         {
  24332.         MENUITEM "~Start",       IDM_START
  24333.         MENUITEM "S~top",        IDM_STOP,,   MIA_DISABLED
  24334.         }
  24335.  
  24336.  This indicates that the Timer item on the top-level menu invokes a submenu
  24337.  containing the items Start and Stop. Multiple levels of submenu nesting are
  24338.  supported but not often used.
  24339.  
  24340.  The text in submenu items can contain a tab character indicated by "\t."
  24341.  The text that follows the tab character appears to the right when the
  24342.  submenu is displayed. You generally use this to indicate the keyboard
  24343.  accelerator for the menu item, as in the submenu invoked by File:
  24344.  
  24345.    SUBMENU "~File",         IDM_FILE
  24346.         {
  24347.         MENUITEM "~New",              IDM_NEW
  24348.         MENUITEM "~Open...",          IDM_OPEN
  24349.         MENUITEM "~Save\tShift+F3",   IDM_SAVE
  24350.         MENUITEM "Save ~As...",       IDM_SAVEAS
  24351.         MENUITEM SEPARATOR
  24352.         MENUITEM "A~bout ConvMenu...",IDM_ABOUT
  24353.         }
  24354.  
  24355.  The F3 key in combination with the Shift key is a keyboard accelerator for
  24356.  Save. This text only indicates to the user what the keyboard accelerators
  24357.  are. I'll discuss later how you make these key combinations function as
  24358.  keyboard accelerators.
  24359.  
  24360.  The ellipsis (...) in some text strings indicates that the item invokes a
  24361.  dialog box. The File submenu also includes the following line, which draws a
  24362.  horizontal line between the Save As and the About menu items:
  24363.  
  24364.    MENUITEM SEPARATOR
  24365.  
  24366.  ID Confusion
  24367.  
  24368.  We first worked with IDs in connection with child windows. A child window ID
  24369.  is assigned by the program when it creates a child window. The child window
  24370.  uses this ID to identify itself to its parent. In the last chapter we began
  24371.  working with resource type IDs and resource name IDs. These IDs identify
  24372.  unique resources within a program.
  24373.  
  24374.  Now we have menu item IDs. Don't confuse these with resource name IDs or
  24375.  child window IDs. The menu item ID identifies a particular menu item within
  24376.  a top-level menu or a submenu. (However, the menu item IDs are sometimes
  24377.  related to child window IDs. For example, IDM_FILE is the menu item ID of
  24378.  
  24379.  the child window ID of the submenu invoked by the File item. But the submenu
  24380.  isn't a child window of the top-level menu. This is obvious, because the
  24381.  submenu is displayed outside the area occupied by the top-level menu.)
  24382.  
  24383.  The Styles and Attributes
  24384.  
  24385.  Every menu item has a style and an attribute, each of which is represented
  24386.  within the Presentation Manager by bit flags within a 16-bit integer. You
  24387.  can override the default style and attribute using identifiers beginning
  24388.  with MIS and MIA in the menu definition.
  24389.  
  24390.  Styles
  24391.  Styles fall into several groups of mutually exclusive options. The first
  24392.  four style bits determine the contents of the visible part of the menu item:
  24393.  
  24394.     Style Bit                  Description
  24395.     MIS_TEXT                   Text string
  24396.     MIS_BITMAP                 Bitmap
  24397.     MIS_SEPARATOR              Horizontal dividing line in submenu
  24398.     MIS_OWNERDRAW              Item that will be drawn by program
  24399.  
  24400.  When you omit a style identifier for a menu item, RC.EXE uses the MIS_STRING
  24401.  style as a default. In CONVMENU's menu, all menu items (except the separator
  24402.  bar in the File menu) have the MIS_STRING style. In the GRAFMENU program
  24403.  shown later in this chapter, we'll use the MIS_BITMAP style. The
  24404.  MIS_SEPARATOR style serves as an alternative to using the MENUITEM SEPARATOR
  24405.  statement. The MIS_OWNERDRAW style requires that your program itself draw
  24406.  the item whenever the menu is displayed. The Presentation Manager sends the
  24407.  client window procedure WM_MEASUREITEM and WM_DRAWITEM messages when the
  24408.  item must be drawn.
  24409.  
  24410.  The next group of style bits determines the organization of the menu items
  24411.  in rows and columns:
  24412.  
  24413.     Style Bit                  Description
  24414.     MIS_BREAK                  Menu item starts in a new row or column
  24415.     MIS_BREAKSEPARATOR         Menu item starts in a new row or column with a
  24416.                                line drawn between the rows or columns
  24417.     MIS_BUTTONSEPARATOR        Menu item is separated by a bar──the user
  24418.                                can't use the cursor movement keys to move to
  24419.                                the item
  24420.  
  24421.  The MIS_BREAK and MIS_BREAKSEPARATOR styles are used most often in submenus
  24422.  that contain a large number of items. These styles aren't required in
  24423.  top-level menus because the Presentation Manager automatically breaks the
  24424.  menu into multiple lines when the window is too narrow to display the menu
  24425.  as a single line. The menu in CONVMENU uses the MIS_BUTTONSEPARATOR style
  24426.  for the "F1=Help" item. This places the item at the far right of the
  24427.  top-level menu.
  24428.  
  24429.  The next set of style bits determines the message that the Presentation
  24430.  Manager sends the program when a menu item has been chosen by the user.
  24431.  Normally, the message is WM_COMMAND. These two bits override that:
  24432.  
  24433.     Style Bit          Description
  24434.     MIS_SYSCOMMAND     Choosing menu item generates a WM_SYSCOMMAND message
  24435.     MIS_HELP           Choosing menu item generates a WM_HELP message
  24436.  
  24437.  The WM_SYSCOMMAND message is usually reserved for system menu items. Because
  24438.  these items generate WM_SYSCOMMAND messages, you can process WM_COMMAND
  24439.  messages from the menu without worrying about receiving system menu
  24440.  messages. The "F1=Help" item in CONVMENU's menu has the MIS_HELP style to
  24441.  generate a WM_HELP message.
  24442.  
  24443.  Although these last two menu item styles have little to do with each other,
  24444.  in a practical sense they are mutually exclusive. In a resource script menu
  24445.  template, the MIS_SUBMENU style is assumed when you use the SUBMENU
  24446.  statement rather than a MENUITEM statement.
  24447.  
  24448.     Style Bit         Description
  24449.     MIS_SUBMENU       Item invokes a submenu
  24450.     MIS_STATIC        Item can't be chosen
  24451.  
  24452.  Attributes
  24453.  These five identifiers determine the attribute of the menu item:
  24454.  
  24455.     Attribute Bit     Description
  24456.     MIA_NODISMISS     If item in submenu is chosen, the submenu remains down
  24457.     MIA_FRAMED        Item is enclosed in a box (top-level menu only; used by
  24458.                       Presentation Manager when item is selected)
  24459.     MIA_CHECKED       Check mark appears to left of item (submenu only)
  24460.     MIA_DISABLED      Item is shown in gray text and can't be chosen
  24461.     MIA_HILITED       Item is shown in reverse video (used by Presentation
  24462.                       Manager when item is selected)
  24463.  
  24464.  The difference between a menu style and a menu attribute is fairly simple: A
  24465.  program can change an item's attribute but not its style (unless the entire
  24466.  item is replaced).
  24467.  
  24468.  The MIA_CHECKED and MIA_DISABLED attributes are used in CONVMENU.RC for the
  24469.  White and Stop menu items respectively. You'll see shortly how a program can
  24470.  change these attributes.
  24471.  
  24472.  Including the Menu in the Standard Window
  24473.  
  24474.  You make the menu part of the standard window by including the FCF_MENU
  24475.  frame creation flag in the definition of flFrameFlags, just as you include
  24476.  the FCF_ICON flag discussed in the last chapter.
  24477.  
  24478.  When the frame flags include FCF_MENU, the second to last parameter of
  24479.  WinCreateStdWindow must be set to the resource name ID of the menu, which,
  24480.  in CONVMENU.RC, is ID_RESOURCE. The Presentation Manager uses this same
  24481.  resource name ID for loading the program's icon when the frame flags include
  24482.  FCF_ICON and for loading the program's keyboard accelerator table when the
  24483.  frame flags include FCF_ACCELTABLE.
  24484.  
  24485.  After the WinCreateStdWindow function returns, you can obtain the handle of
  24486.  the top-level menu by using the following function:
  24487.  
  24488.    hwndMenu = WinWindowFromID (hwndFrame, FID_MENU) ;
  24489.  
  24490.  Or, within the client window procedure, you can use
  24491.  
  24492.    hwndMenu = WinWindowFromID (
  24493.                   WinQueryWindow (hwnd, QW_PARENT, FALSE),
  24494.                   FID_MENU) ;
  24495.  
  24496.  Often the client window procedure obtains the window handle of the menu
  24497.  during the WM_CREATE message and stores it in a static variable for later
  24498.  use.
  24499.  
  24500.  Receiving Menu Messages
  24501.  
  24502.  The Presentation Manager sends the frame window procedure WM_COMMAND
  24503.  messages when the user chooses an enabled menu item from the menu. (This
  24504.  message will be WM_SYSCOMMAND or WM_HELP if the menu item style includes the
  24505.  MIS_SYSCOMMAND or MIS_HELP style bit.) The frame window passes the messages
  24506.  to the client window procedure. If a disabled menu item is chosen, no
  24507.  WM_COMMAND message is generated.
  24508.  
  24509.  The mp1 and mp2 parameters that accompany a WM_COMMAND message are shown
  24510.  below:
  24511.  
  24512.     WM_COMMAND Parameters     Description
  24513.     SHORT1FROMMP (mp1)        Menu item ID
  24514.     SHORT1FROMMP (mp2)        CMDSRC_MENU
  24515.     SHORT2FROMMP (mp2)        Nonzero if selected by mouse, 0 if selected by
  24516.                               keyboard
  24517.  
  24518.  WM_COMMAND is the same message that a push button window sends its owner.
  24519.  For a push button, the low USHORT of mp1 is the child window ID, and the low
  24520.  USHORT of mp2 is CMDSRC_PUSHBUTTON. Keyboard accelerators send WM_COMMAND
  24521.  messages with the low USHORT of mp1 equal to CMDSRC_ACCELERATOR. If you're
  24522.  receiving WM_COMMAND messages from menus, accelerators, and push buttons,
  24523.  it's easiest to ignore mp2 and test only the low USHORT of mp1. You should
  24524.  thus make all ID numbers unique unless you deliberately want the program to
  24525.  process WM_COMMAND messages from two or more different sources in the same
  24526.  way. (This is often the case with keyboard accelerators, because you use
  24527.  them to duplicate menu items.) As you learned in Chapter 11, you can also
  24528.  use the COMMANDMSG macro for decoding the message parameters of a WM_COMMAND
  24529.  message. For example, the expression
  24530.  
  24531.    COMMANDMSG (&msg) -> cmd
  24532.  
  24533.  is the menu item ID.
  24534.  
  24535.  In ClientWndProc, the processing of the WM_COMMAND message looks like this:
  24536.  
  24537.    case WM_COMMAND:
  24538.         switch (COMMANDMSG (&msg) -> cmd)
  24539.              {
  24540.                   [case statements for menu item IDs]
  24541.              }
  24542.         break ;
  24543.  
  24544.  You'll note that the switch and case construction includes case statements
  24545.  only for IDs associated with menu items in the menu's MENUITEM statements.
  24546.  The window procedure never receives WM_COMMAND messages for the menu item
  24547.  IDs in SUBMENU statements because these items invoke submenus and aren't
  24548.  commands in themselves. The WM_COMMAND processing in CONVMENU.C also lacks a
  24549.  case statement for IDM_HELP because that menu item generates a WM_HELP
  24550.  message. In the WM_COMMAND message processing, the IDM_NEW, IDM_OPEN,
  24551.  IDM_SAVE, IDM_SAVEAS, and IDM_ABOUT items cause the program to display
  24552.  message boxes. Normally, these items would cause the program to create and
  24553.  display a dialog box.
  24554.  
  24555.  Working with Checked Menu Items
  24556.  
  24557.  The submenu invoked by the Background item on CONVMENU's top-level menu
  24558.  allows the user to choose one of five colors that the program uses to color
  24559.  the background of the client window:
  24560.  
  24561.    SUBMENU "~Background",   IDM_BACKGROUND
  24562.         {
  24563.         MENUITEM "~White\tCtrl+W",        IDM_WHITE,, MIA_CHECKED
  24564.         MENUITEM "~Light Gray\tCtrl+L",   IDM_LTGRAY
  24565.         MENUITEM "~Gray\tCtrl+G",         IDM_GRAY
  24566.         MENUITEM "~Dark Gray\tCtrl+D",    IDM_DKGRAY
  24567.         MENUITEM "~Black\tCtrl+B",        IDM_BLACK
  24568.         }
  24569.  
  24570.  When the Presentation Manager first creates the window, the White item
  24571.  appears with a check mark to the left of the text. Check marks are used most
  24572.  often for mutually exclusive menu options, as is the case here.
  24573.  
  24574.  Within ClientWndProc, the sCurrentBackground variable is initialized with
  24575.  the menu item ID of the checked item:
  24576.  
  24577.    static SHORT sCurrentBackground = IDM_WHITE ;
  24578.  
  24579.  When ClientWndProc receives a WM_COMMAND message for one of the five items
  24580.  in this submenu, it must remove the check mark from the item currently
  24581.  checked, add a check mark to the item that the user has chosen, and change
  24582.  the color of the client window.
  24583.  
  24584.  Processing of the WM_COMMAND message is the same for all five items in this
  24585.  submenu:
  24586.  
  24587.    case WM_COMMAND:
  24588.          switch (COMMANDMSG (&msg) -> cmd)
  24589.              {
  24590.                   [other program lines]
  24591.              case IDM_WHITE:
  24592.  
  24593.  
  24594.  To process these commands, CONVMENU first removes the check mark from the
  24595.  menu item that is currently checked. The ID of that menu item is stored in
  24596.  sCurrentBackground. The program can remove the check mark by sending the
  24597.  menu window a MM_SETITEMATTR message:
  24598.  
  24599.     WinSendMsg (hwndMenu, MM_SETITEMATTR,
  24600.                   MPFROM2SHORT (sCurrentBackground, TRUE),
  24601.                   MPFROM2SHORT (MIA_CHECKED, 0)) ;
  24602.  
  24603.  The mp1 parameter of this message contains two USHORT values. The low USHORT
  24604.  of mp1 has the ID of the menu item to be changed. However, you're sending
  24605.  this message to the window whose handle is hwndMenu. That's the window
  24606.  handle of the top-level window, not the submenu that contains the five color
  24607.  items. The high USHORT of mp1 must be set to TRUE to tell the window
  24608.  procedure for the top-level menu to search through the submenus for a menu
  24609.  item with an ID equal to sCurrentBackground.
  24610.  
  24611.  The low USHORT of mp2 contains the attribute bit (or bits) to be changed. In
  24612.  this case, we want to change the MIA_CHECKED attribute bit. The high USHORT
  24613.  of mp2 is set to the new value of these attribute bits──in this case 0.
  24614.  This removes the MIA_CHECKED attribute from the menu item.
  24615.  
  24616.  CONVMENU sets sCurrentBackground equal to the item the user has chosen from
  24617.  the menu:
  24618.  
  24619.    sCurrentBackground = COMMANDMSG (&msg) -> cmd ;
  24620.  
  24621.  The program then sends the menu another MM_SETITEMATTR message. This is
  24622.  identical to the first message except that the high USHORT of mp2 is set to
  24623.  MIA_CHECKED:
  24624.  
  24625.    WinSendMsg (hwndMenu, MM_SETITEMATTR,
  24626.                   MPFROM2SHORT (sCurrentBackground, TRUE),
  24627.                   MPFROM2SHORT (MIA_CHECKED, MIA_CHECKED)) ;
  24628.  
  24629.  The menu item chosen by the user now has the MIA_CHECKED attribute, and a
  24630.  check mark is drawn to the left of the item.
  24631.  
  24632.  Most WM_COMMAND processing of mutually exclusive check-marked menu items
  24633.  requires little more than these three statements. Structurally, the code is
  24634.  very similar to that used in the DRAWLINE program in Chapter 11 to check
  24635.  and uncheck radio buttons. In CONVMENU, the only job left is to repaint the
  24636.  client window with the new color. This is accomplished by invalidating the
  24637.  window to generate a WM_PAINT message:
  24638.  
  24639.    WinInvalidateRect (hwnd, NULL, FALSE) ;
  24640.  
  24641.  During the WM_PAINT message, CONVMENU calls GpiCreateLogColorTable to use
  24642.  RGB color indices, obtains the dimensions of the client window, and uses
  24643.  WinFillRect to color it:
  24644.  
  24645.    WinQueryWindowRect (hwnd, &rcl) ;
  24646.  
  24647.    WinFillRect (hps, &rcl,
  24648.                 colBackground [sCurrentBackground - IDM_WHITE]) ;
  24649.  
  24650.  The colBackground array is initialized in ClientWndProc to contain the five
  24651.  color values corresponding to the five menu items:
  24652.  
  24653.    static COLOR colBackground [] = {
  24654.                                    0xFFFFFFL, 0xC0C0C0L, 0x808080L,
  24655.                                    0x404040L, 0x000000L
  24656.                                    } ;
  24657.  
  24658.  The only assumption the program logic makes is that the five menu item ID
  24659.  numbers are consecutive. The CONVMENU.H file contains a little note to this
  24660.  effect.
  24661.  
  24662.    #define IDM_WHITE        30
  24663.    #define IDM_LTGRAY       31
  24664.    #define IDM_GRAY         32   // Program logic assumes these
  24665.    #define IDM_DKGRAY       33   // five numbers are consecutive
  24666.    #define IDM_BLACK        34
  24667.  
  24668.  Enabling and Disabling Menu Items
  24669.  
  24670.  Another useful attribute of menu items is MIA_DISABLED. When a menu item is
  24671.  disabled, it appears in gray text. A disabled menu item doesn't generate a
  24672.  WM_COMMAND message.
  24673.  
  24674.  CONVMENU uses disabled menu items on its Timer submenu. When the program
  24675.  begins, the Stop item is disabled, as indicated in the menu definition in
  24676.  CONVMENU.RC:
  24677.  
  24678.    SUBMENU "~Timer",        IDM_TIMER
  24679.         {
  24680.         MENUITEM "~Start",       IDM_START
  24681.         MENUITEM "S~top",        IDM_STOP,,    MIA_DISABLED
  24682.         }
  24683.  
  24684.  It makes no sense to stop the timer when it hasn't been started yet. When
  24685.  you choose Start from the menu, CONVMENU disables the Start item and enables
  24686.  Stop.
  24687.  
  24688.  We could handle this enabling and disabling in the same way that we removed
  24689.  and added the check mark, with some additional logic required for starting
  24690.  and stopping the timer. However, in CONVMENU, the processing of WM_COMMAND
  24691.  messages for IDM_START and IDM_STOP doesn't alter the menu item attributes.
  24692.  Instead, IDM_START simply starts the timer, and IDM_STOP stops it:
  24693.  
  24694.    case IDM_START:
  24695.         if (WinStartTimer (hab, hwnd, ID_TIMER, 1000))
  24696.                   fTimerGoing = TRUE ;
  24697.         else
  24698.              WinMessageBox (HWND_DESKTOP, hwnd,
  24699.                   "Too many clocks or timers",
  24700.                   szClientClass, 0,
  24701.                   MB_OK | MB_ICONEXCLAMATION) ;
  24702.         return 0 ;
  24703.  
  24704.    case IDM_STOP:
  24705.         WinStopTimer (hab, hwnd, ID_TIMER) ;
  24706.         fTimerGoing = FALSE ;
  24707.         return 0 ;
  24708.  
  24709.  CONVMENU enables and disables the menu items while processing the
  24710.  WM_INITMENU message. The Presentation Manager sends a window procedure a
  24711.  WM_INITMENU message when it's about to display a submenu. The low USHORT of
  24712.  mp1 is the ID of the top-level menu item that invokes the submenu. The
  24713.  program can take this opportunity to change the submenu. CONVMENU processes
  24714.  the WM_INITMENU message as shown on the next page.
  24715.  
  24716.    case WM_INITMENU:
  24717.         switch (SHORT1FROMMP (mp1))
  24718.              {
  24719.              case IDM_TIMER:
  24720.                   WinSendMsg (hwndMenu, MM_SETITEMATTR,
  24721.                             MPFROM2SHORT (IDM_START, TRUE),
  24722.                             MPFROM2SHORT (MIA_DISABLED,
  24723.                                       !fTimerGoing &&
  24724.                                  WinQuerySysValue (HWND_DESKTOP, SV_CTIMERS) ?
  24725.                                       0 : MIA_DISABLED)) ;
  24726.  
  24727.                   WinSendMsg (hwndMenu, MM_SETITEMATTR,
  24728.                             MPFROM2SHORT (IDM_STOP, TRUE),
  24729.                             MPFROM2SHORT (MIA_DISABLED,
  24730.                                  fTimerGoing? 0 : MIA_DISABLED)) ;
  24731.                   return 0 ;
  24732.              }
  24733.         break ;
  24734.  
  24735.  CONVMENU ignores WM_INITMENU messages unless they involve the Timer submenu.
  24736.  
  24737.  The first WinSendMsg call sets the MIA_DISABLED bit on the Start item if the
  24738.  timer is already active (indicated by a TRUE value of fTimerGoing) or if no
  24739.  timers are available (which you can determine from the WinQuerySysValue
  24740.  function). The second WinSendMsg call sets the MIA_DISABLED bit on the Stop
  24741.  item if the timer isn't currently active.
  24742.  
  24743.  Handling the Exit Command
  24744.  
  24745.  When ClientWndProc receives a WM_COMMAND message with the IDM_EXIT menu item
  24746.  ID, it sends itself a WM_CLOSE message:
  24747.  
  24748.    case IDM_EXIT:
  24749.         WinSendMsg (hwnd, WM_CLOSE, 0L, 0L) ;
  24750.         return 0 ;
  24751.  
  24752.  WM_CLOSE is the same message the system menu sends the window procedure when
  24753.  the user chooses Close from the system menu. Most of the programs I've
  24754.  written so far have not processed the WM_CLOSE message but simply have
  24755.  passed it on to WinDefWindowProc. WinDefWindowProc responds to the WM_CLOSE
  24756.  message by posting a WM_QUIT message to the program's message queue, which
  24757.  causes the message loop in main to end and the program to terminate.
  24758.  
  24759.  Some programs (those that work with files, for example) will want
  24760.  confirmation that the user really wants to end the program.
  24761.  
  24762.  A program can be terminated not only from an Exit item on the program's menu
  24763.  or from the Close item on the system menu, but from the Task Manager as
  24764.  well. The user can select the Close option on the Task Manager's Task menu
  24765.  or the Shutdown option. In both of these cases, the Task Manager posts a
  24766.  WM_QUIT message to the program's message queue.
  24767.  
  24768.  To handle all of these cases, a program that needs to request confirmation
  24769.  from the user before terminating must do so after receiving a WM_QUIT
  24770.  message. This requires that you add some logic to the message loop. Here's
  24771.  how CONVMENU does it:
  24772.  
  24773.    while (TRUE)
  24774.         {
  24775.         while (WinGetMsg (hab, &qmsg, NULL, 0, 0))
  24776.              WinDispatchMsg (hab, &qmsg) ;
  24777.  
  24778.         if (MBID_OK == WinMessageBox (HWND_DESKTOP, hwndClient,
  24779.                                       "Really want to end program?",
  24780.                                       szClientClass, 0,
  24781.                                       MB_OKCANCEL | MB_ICONQUESTION))
  24782.  
  24783.              break ;
  24784.         WinCancelShutdown (hmq, FALSE) ;
  24785.         }
  24786.    WinDestroyWindow (hwndFrame) ;
  24787.  
  24788.  The WM_QUIT message causes WinGetMsg to return 0 and drop out of the message
  24789.  loop. CONVMENU then displays a message box with OK and Cancel buttons and
  24790.  asks if the user really wants to end the program. If the user answers by
  24791.  pressing OK, the break statement is executed and termination begins with
  24792.  WinDestroyWindow.
  24793.  
  24794.  Otherwise, the program calls WinCancelShutdown (which halts any system
  24795.  shutdown that might have been initiated by the Task Manager), ignores the
  24796.  WM_QUIT message, and goes back to the message loop.
  24797.  
  24798.  In CONVMENU.RC, the Exit menu item is defined like this:
  24799.  
  24800.    MENUITEM "E~xit ConvMenu\tF3",   IDM_EXIT
  24801.  
  24802.  It could have been defined like this:
  24803.  
  24804.    MENUITEM "E~xitConvMenu\tF3",  SC_CLOSE, MIS_SYSCOMMAND
  24805.  
  24806.  This causes the Exit item to generate a WM_SYSCOMMAND message with the low
  24807.  USHORT of mp1 equal to SC_CLOSE. This is the same message generated from the
  24808.  system menu when the user selects Close. WinDefWindowProc processes this
  24809.  message by sending the window procedure a WM_CLOSE message. If I had used
  24810.  this, I wouldn't have required the IDM_EXIT identifier or the code to send
  24811.  the window procedure a WM_CLOSE message.
  24812.  
  24813.  The WM_HELP Message
  24814.  
  24815.  The menu template in CONVMENU.RC includes this menu item:
  24816.  
  24817.    MENUITEM "F1=Help",      IDM_HELP, MIS_HELP | MIS_BUTTONSEPARATOR
  24818.  
  24819.  The MIS_BUTTONSEPARATOR style puts the text at the far right of the
  24820.  top-level menu. The MIS_HELP style indicates that the menu item generates a
  24821.  WM_HELP message.
  24822.  
  24823.  The mp1 and mp2 parameters that accompany the WM_HELP message are the same
  24824.  as those for WM_COMMAND messages:
  24825.  
  24826.     WM_HELP Parameters        Description
  24827.     SHORT1FROMMP (mp1)        Menu item ID
  24828.     SHORT1FROMMP (mp2)        CMDSRC_MENU
  24829.     SHORT2FROMMP (mp2)        Nonzero if selected by mouse, 0 if selected by
  24830.                               keyboard
  24831.  
  24832.  A push button can also generate a WM_HELP message if it's given the style
  24833.  BS_HELP. For