Programming for the OS/2 PM in C:Printing

From EDM2
Jump to: navigation, search

by Rick Papo

<< The File Dialog and Help - Index - The Clipboard >>

Part VII. - Printing

Over the past few months we have developed a simple paint program which accepts and stores a collection of straight lines forming a picture, and allows you to save that picture to a disk file or restore it from the disk file afterwards. All this is nice, but it would be much nicer if we could send the results to the printer. Of course, you already could use the PrintScreen function to do this, but if you did that you also got a printed image of the program's frame and titlebar too. So let's see what must be done to add a simple Print function to the program.

The first thing that must be done is busy-work. We must add a Print option to the File menu, and update all the different things that go with a menu option. In the header file (.H) we must define an identifier (IDM_PRINT) for the print command. In the resource script (.RC) file we must add a new entry to the File menu definition. In this case, I added a separator before it to separate it from the other groups of functions in the File menu, save/restore and exit. The new file menu definition looks like this:

SUBMENU "~File",                   IDM_FILE_MENU
        {
            MENUITEM "~New\tCtrl+N",         IDM_NEW
            MENUITEM "~Open...\tCtrl+O",     IDM_OPEN
            MENUITEM "~Save\tF2",            IDM_SAVE
            MENUITEM "Save ~As...",          IDM_SAVEAS
            MENUITEM SEPARATOR
            MENUITEM "~Print...\tCtrl+P",    IDM_PRINT
            MENUITEM SEPARATOR
            MENUITEM "E~xit\tF3",            IDM_EXIT
        }

Since I want Ctrl+P as a keyboard shortcut (accelerator) for the Print command, I also add the following lines to the ACCELTABLE definition:

"P", IDM_PRINT, CONTROL
"p", IDM_PRINT, CONTROL

We want context sensitive help for the File/Print menu entry, so the following line gets added to the main HELPSUBTABLE definition:

HELPSUBITEM IDM_PRINT, IDM_PRINT

PrintingWe must provide a help panel for IDM_PRINT. The following text gets added to the IPF file:

:h3 res=IDM_PRINT.Print...
When this menu item is selected, the current drawing is printed
on the system default printer, with the currently specified
job properties.

For the sake of completeness, the following line gets added to the list of links in the IDM_FILE topic also:

:li.:link reftype=hd res=IDM_PRINT.Print...:elink.

The last piece of housekeeping to do is to add a clause in the processing of the WM_COMMAND message to handle the receipt of the IDM_PRINT command.

Fine. We are done with housekeeping. Now for the real work: making the print function work. For the sake of simplicity, we will not implement an elaborate print function this month. Rather, we will simply print to the system default printer, using the currently defined default job properties. We may return to improvements like a dialog to select the printer to use and the job properties, and printing in the background, at a future time.

The OS/2 Presentation Manager uses a very flexible graphics subsystem known as the GPI, or Graphics Programming Interface, for nearly everything related to generating text or graphics on a window. We've already seen a very small sample of it in the GpiMove and GpiLine functions already being used to draw the lines on the window. The real power of the GPI, however, is the power it has to hide the nature of the actual graphic output device from the programmer. Let's see how this is done.

Nearly all graphic functions in the GPI require a handle to a special object called the Presentation Space (PS). The Presentation Space is independent of the actual physical device used for output. The PS does not perform the physical output, rather another object, the Device Context (DC), is used for this. Unlike the PS, which cares nothing for the physical device characteristics, the DC is all about physical characteristics. As you might expect, outputting to different devices depends on how you create the DC object that your PS will use.

Output to the video screen is required so often that special helper functions were provided to make the job as painless and quick as possible. Video output is almost always of two flavours: painting in response to a WM_PAINT message, and arbitrary painting not requested by the system. For the first situation, the following code is used:

HPS hPS = WinBeginPaint (hwnd, 0, 0);
/* Perform window update here. */
WinEndPaint (hPS);

For the second situation, an unsolicited change to the window graphics, the following code would be used instead:

HPS hPS = WinGetPS (hwnd);
/* Perform window update here. */
WinReleasePS (hPS);

The reason for the difference between the two lies in how the Presentation Manager determines that a portion of a window needs updating. When you use WinBeginPaint to obtain your presentation space, that presentation space is very special: it is constrained (clipped) so that whatever graphics you request be done, only those that act on the portion of the window that needed repainting actually get done. The function has the side-effect of hiding the mouse pointer temporarily too, so that it does not interfere with the update. WinEndPaint marks the region as having been updated and restores the mouse pointer. When you use WinGetPS/WinReleasePS you get unrestricted access to the entire visible area of your program's window.

Output to the printer requires a little more preparation. For unknown reasons, no significant helper functions were provided to set up the device context and presentation space. We have to do it the hard way. Once we have the presentation space handle, however, we can use it almost exactly as you would for the video screen.

The first thing we have to do is get some information about the printer we will use. There are functions to get information about all the printers on the system, and we use them here to get a list of all the printers defined on the system:

SplEnumQueue( 0, 3, 0, 0, &cReturned, &cTotal, &cbNeeded, 0 );
DosAllocMem ( &pBuf, cbNeeded, PAG_READ|PAG_WRITE|PAG_COMMIT );
cbBuf = cbNeeded;
SplEnumQueue ( 0, 3, pBuf, cbBuf, &cReturned, &cTotal, &cbNeeded, 0 );

The first call to SplEnumQueue found out how much memory was required to get the defined printer information, then memory was allocated for the information, and then a second call to SplEnumQueue was used to get the actual information. Having this list, we can determine which printer we are going to use:

prq = (PPRQINFO3)pBuf;
for ( i=0; ifsType & PRQ3_TYPE_APPDEFAULT ) ) 
	pQueueInfo = prq;
if ( pQueueInfo == 0 )
        pQueueInfo = (PPRQINFO3)pBuf;

This code fragment scanned the list of defined printers for the first printer marked as default. If no such printer was found, the first printer in the list was chosen. If none was found at all, no printing can be done.

Provided we have found a printer description, we can now get a device context for that printer. The following code can be used for this purpose:

DriverName = (char*) malloc ( strlen(pQueueInfo->pszDriverName)+1 );
strcpy ( DriverName, pQueueInfo->pszDriverName );
p = strchr ( DriverName, '.' );
if ( p ) *p = 0;

DeviceOpenParms.pszDriverName = DriverName;
DeviceOpenParms.pszLogAddress = pQueueInfo->pszName;
DeviceOpenParms.pdriv = pQueueInfo->pDriverData;
DeviceOpenParms.pszDataType = "PM_Q_STD";
hDC = DevOpenDC ( WinQueryAnchorBlock(hwnd), OD_QUEUED, "*", 4,
		  (unsigned char**)&DeviceOpenParms, 0 );

The first few lines of this segment extract the printer driver name from the printer description. The field pszDriverName contains a name in the format 'Driver.Model', and we only need the Driver portion of the name. The second part of this segment uses this driver name and several other pieces of information from the printer description to build the 'device open parameter' block, which is the main information needed to create a device context. We are creating a standard queued (spooled) device context here.

Once the device context has been created successfully, we must do one more thing before we create our presentation space. Since we are creating a queued print job, we need to send a job start notification to the spooler, which is done with the following statement:

DevEscape (hDC, DEVESC_STARTDOC, strlen(Title), Title, 0, 0);

The title could be anything you want, and will appear as the title of the print job in the printer folder, should you happen to have it open at the time you request the printing be done.

Creating the presentation space is fairly simple, though it is not done with either of the shortcut functions described earlier. The only code required is this:

SIZEL PageSize = {0, 0};
HPS hPS = GpiCreatePS ( WinQueryAnchorBlock(hwnd), hDC,
                       &PageSize, PU_PELS | GPIA_ASSOC );

Here we are creating a presentation space with pixel units, associated with the printer device context we just created, and having a size determined by the device itself, according to its current settings (form, etc). Once we have this presentation space, we can start outputting graphics at once.

Once you have completed the graphic output, you simply destroy the presentation space with the GpiDestroyPS function (the graphics already reside in the device context) and notify the spooler that we're done:

DevEscape ( hDC, DEVESC_ENDDOC, 0, 0, &OutCount, (PBYTE)&Job );

Once this notification has been done, you can close the device context with the DevCloseDC function. We're done.

The sample program has all this processing built into it, with the addition of some code to insure that the image printed has exactly the same size and proportion as the image shown on the screen. I will not discuss the more advanced GPI functions used for this ...not this month.

<< The File Dialog and Help - Index - The Clipboard >>