Chapter 6 "NPP Methods: Starting the Plug-In," shows how Navigator loads the plug-in and starts an instance. Chapter 7, "NPP Methods: Handling Interaction," describes additional tasks your plug-in may have to do to interact with the user and with the data stream itself.
Most plug-ins are also called on to print themselves, either as embedded content in a Navigator window or as a full document. All plug-in instances are eventually destroyed, and any associated window the instance has must be closed.
This chapter deals with these "additional duties" of the plug-in: printing, closing its windows, and destroying itself on request.
Recall that you, as the programmer, write all NPP functions. These NPP functions are called by Navigator. NPP_Print() is unique in that, under the right circumstances, Navigator calls it twice. NPP_Print() is Navigator's request that your plug-in print itself.
An HTML author can include a plug-in using an <EMBED> statement. Alternatively, the HTML author can put a link in his or her Web page that points to a whole new document.
If your plug-in is called through <EMBED>, it is an embedded plug-in. If your plug-in is invoked on a document, it is a full-page plug-in.
If your plug-in is embedded, Netscape calls the plug-in's NPP_Print() function once. If your plug-in is full-page, Netscape calls your plug-in's NPP_Print() once to find out if the plug-in handles the whole printing process. If it does not, Netscape calls your plug-in again, also through NPP_Print(), to have it handle the printing of the content.
Confused? NPP_Print() is a busy place, particularly for a full-page plug-in. This section shows an example of how to handle NPP_Print() in an embedded plug-in. NPP_Print() is defined by the following line:
void NPP_Print(NPP instance, NPPrint *platformPrint);
Here, instance is the usual instance that is passed from Netscape to nearly all NPP functions and platformPrint is a structure whose mode member can take on the NP_EMBED and NP_FULL values. When the user elects to print a page that has an embedded plug-in, Netscape calls NPP_Print() for the plug-in with platformPrint->mode set to NP_EMBED.
The full declaration of platformPrint is as follows:
typedef struct _NPPrint
{
uint16 mode; /* NP_FULL or NP_EMBED */
union
{
NPFullPrint fullPrint; /* if mode is NP_FULL */
NPEmbedPrint embedPrint;/* if mode is NP_EMBED */
} print;
} NPPrint;
Note that the union statement says that the two data members fullPrint and embedPrint occupy the same space-only one member can be present in an instance at a time. If mode is set to NP_EMBED, the union print has embedPrint.
The structure of embedPrint is as follows:
typedef struct _NPEmbedPrint
{
NPWindow window;
void* platformPrint; /* Platform-specific printing info */
} NPEmbedPrint;
The NPWindow in this structure is the window the plug-in should draw into for printing. Recall from Chapter 6 "NPP Methods: Starting the Plug-In," that an NPWindow is defined as shown in the following lines:
typedef struct _NPWindow
{
void* window; /* Platform specific window handle */
uint32 x; /* Position of top left corner relative */
uint32 y; /* to a netscape page. */
uint32 width; /* Maximum window size */
uint32 height;
NPRect clipRect; /* Clipping rectangle in port coordi
/* Used by Mac only. */
#ifdef XP_UNIX
void * ws_info; /* Platform-dependent additional data */
#endif /* XP_UNIX */
} NPWindow;
Your plug-in should draw into the window
member to print its contents.
| Caution |
On a Windows machine, the coordinates of the window rectangle are in twips. In the MM_TWIPS mapping mode the logical unit, twip, is 1/20 of a point or 1/1440 of an inch, and the base unit is in physical inches. This design is never used with a display (because displays come in all sizes and the physical inch is meaningless), but it is perfectly appropriate when drawing to a printer. Make sure that you call DPtoLP() to convert these points when you print text into the window. DPtoLP() converts device coordinates to logical coordinates using the current mapping mode. |
Special Features on the Macintosh The second field of NPEmbedPrint, platformPrint, says that it has Platform-specific printing info. Specifically, on the Macintosh, platformPrint has a THPrint, a handle to a TPrint. The TPrint, in turn, is an Apple-defined structure that includes the following:
| Caution |
The TPrint structure is strictly read-only. Make sure that your code wraps reads of the structure in access methods-Apple changes the details of this structure from time to time to keep up with advances in printing technology. If you use access methods, you can take advantage of Apple's changes. |
By the time Navigator is ready for your plug-in to print its contents,
it has already given the user a Print
dialog. As a Macintosh
user, you will recognize many of these parameters from the Page
Setup or Print
dialogs. Navigator also fills in information
it gets from the printer driver.
| Tip |
The TPrint has a rich array of detailed information about the printing job-more than most plug-ins will ever use. Unless your plug-in has special needs, don't access the TPrint record-simply draw the contents of your plug-in into the window provided, and let Navigator and the printer driver handle the rest. This approach leaves your code more portable to other platforms and makes it more likely that your plug-in will work correctly with all printers (including future versions of the printer driver). |
When Navigator is ready to print your full-page plug-in, it first gives you an opportunity to take control of the printing process. Some documents benefit from this opportunity. For example, Figures 8.1 and 8.2 contrast the standard Print dialog with the Print dialog provided by Microsoft Word.
To allow you to take over the printing process, Navigator calls NPP_Print() with platformPrint->mode set to NP_FULL and union member print filled with an NPFullPrint. Navigator defines an NPFullPrint as follows:
typedef struct _NPFullPrint
{
NPBool pluginPrinted; /* Set TRUE if plugin handled fullscreen
/* printing */
NPBool printOne; /* TRUE if plugin should print one copy
/* to default printer */
void* platformPrint; /* Platform-specific printing info */
} NPFullPrint;
The platformPrint member has the same information it has in NPEmbedded. Using the NPBool printOne is self-explanatory and tells the plug-in that the user just wants a quick, no-dialog print.
The NPBool pluginPrinted
is the member that distinguishes full-page printing from embedded
plug-in printing. If you commandeer the printing process, providing
your own dialog boxes and driving the printer yourself, set pluginPrinted
to true. If Navigator sees this flag set to true, it bypasses
its own Print dialog.
| Tip |
Although Netscape allows your full-page plug-in to take over the printing process, you need to use this option only if your plug-in has special needs. Most full-page plug-ins can set pluginPrinted to false, allowing Navigator to handle the Print dialog. |
If you set pluginPrinted
to false, Navigator initiates the printing process. When it is
ready for your content, Navigator calls your plug-in again-through
NPP_Print()-with platformPrint->mode
set to NP_EMBED.
| Tip |
Your Macintosh plug-in may need access to information from the printer driver or the Print dialog. But if you don't need to add items to the standard dialog or otherwise control the print process, simplify your design by returning false in pluginPrinted. Then, when Navigator calls NPP_Print() the second time, read platformPrint to get the information you need. Alas, Netscape doesn't provide similar information for Windows or UNIX plug-ins in Navigator version 3.0. On these platforms you must take control of the printing process if you want detailed information about the printer or the print job. |
If you save data with your full-page plug-in instance (using NPSavedData, described in a following section of this chapter, "Saving Instance Data"), consider including a copy of the TPrint. Then, if you are later asked to print again, you can reuse the TPrint to handle a printOne request.
Call PrValidate() to ensure that the user hasn't changed the active printer or printer driver since the last print request. If the TPrint changed-indicated by a TRUE return from PrValidate()-or if the user makes a printOne request before he or she has completed a Print dialog, just call PrintDefault() to preset the fields of the TPrint.
Printing in Windows Printing is supported by the Microsoft Foundations Classes (MFCs) CView class. If your full-page plug-in handles multipage documents, override CView's OnPrint() member in your own derived class.
In your view's version of OnPrint(), look up the size of the printer's page and use this information to adjust the clipping region of the device context associated with the print window. After the "page" is defined, call the view's OnDraw() member to output the content.
Even today, not all printers accept bitmaps. You can find out if the printer associated with your print window properly renders a bitmap by including the following code (in which hDC is the device context associated with the window):
if (!(GetDeviceCaps(hDC, RASTERCAPS)
& RC_BITBLT))
{
// printer cannot display bitmaps
.
.
.
}
When you are ready to print, you will want to put up a Cancel dialog, in case the user decides to stop the print job before it completes. Putting up this kind of dialog box is a four-step process:
Here's the code for these four steps:
BOOL bContinuePrinting = TRUE; SetAbortProc(hDC, AbortProc); hdlgCancel = CreateDialog(hinst, (LPTSTR) "AbortDlg", hWnd, (DLGPROC) AbortPrintJob); EnableWindow(hWnd, FALSE);
The abort procedure can be very simple, as follows:
BOOL CALLBACK AbortProc(HDC hDC, int nCode)
{
MSG msg;
while (PeekMessage((LPMSG) &msg, (HWND) NULL, 0, 0, PM_REMOVE))
{
// if the message isn't for us, send it on
if (!IsDialogMessage(hdlgCancel, (LPMSG) &arg))
{
TranslateMessage((LPMSG) &msg);
DispatchMessage((LPMSG) &msg);
}
}
return bContinuePrinting; // set to FALSE by Cancel button
}
Use your development environment to construct a suitable Cancel dialog. Write a dialog procedure that includes the following code:
switch (message)
.
.
.
case WM_COMMAND: // the Cancel button is the only control
{
bContinuePrinting = FALSE;
return TRUE:
break;
}
.
.
.
}
Now when Navigator tells you to print, you can put up the common Print dialog (possibly with customizations). Verify that the printer you selected can print your document. Put the Cancel dialog box on-screen.
If the user clicks the Cancel button, the WM_COMMAND message is sent to the AbortPrintJob function, which sets bContinuePrinting to FALSE. When the plug-in sees bContinuePrinting go FALSE, it stops drawing pages and cleans up from printing.
After the Cancel dialog box is up, the plug-in calls StartDoc(), and then StartPage(). These Windows functions alert Windows that the print data is coming.
Then, based on the data returned from the Print dialog, you find the page(s) the user wants to print and draw their contents to the print window. Call EndPage() after each page is printed.
When all the data was sent (or bContinuePrinting becomes false), call EndDoc(). Finally, call EnableWindow() to return control to the plug-in window, call DestroyWindow() on the cancel dialog, and call DeleteDC() on the device context allocated by StartDoc().
For most plug-ins, this is all that's needed. But if your plug-in draws text, you may want to think about fonts and text metrics. Microsoft Windows comes with various fonts for the screen-Courier, Helvetica, and Times Roman, to name a few.
Most printers include most of these common fonts. But not all
printers use all the standard fonts, and users can add fonts to
their system. Therefore, a user may have text on the screen in
a font that is unavailable in the printer.
| Note |
Windows supports many font technologies. A Postscript printer uses, of course, a PostScript font. This font may be downloaded from the computer or stored in the printer's read-only memory (ROM). Hewlett-Packard's PCL printers use HP's Printer Control Language (PCL). Both Microsoft and HP provide drivers for these printers, which offer good font quality. On the PC screen, Windows can use bit-mapped fonts (known as raster fonts). If a user prints a document that is being viewed with raster fonts, Windows must try to match a printer font (such as PostScript or PCL) to the raster font. TrueType technology is a compromise between raster fonts and printer fonts. TrueType fonts are built from mathematical models (like PostScript fonts) but can be displayed both on-screen and on the printed page. For more information on Windows fonts see Chapter 8 "Working With Fonts," in Platinum Edition Using Windows 95 (Que, 1996). |
Windows includes an elaborate penalty weighting algorithm based on ten font characteristics, including character sets, pitch, family, face name, height, width, and various styles. Windows uses this algorithm to choose a printer font that should be a close match to the screen font.
You, the programmer, should control which font the plug-in uses
and match it with the available printer font. Otherwise, the printing
may be done in a font that is quite unlike the font used on-screen.
| Tip |
If your plug-in chooses the screen font, choose a TrueType font whenever possible. One big advantage of TrueType is that the printer version and the screen version match exactly. |
For better control over the printed page, Windows programmers can call GetTextMetrics() to fill a TextMetric data structure with information about the currently selected font. The TextMetric structure includes information such as the following:
For pinpoint control of key pieces of text (such as titles), use GetTextExtentPoint32(). This function computes the width and height of a specified string in the current font. Use this information to center, scale, or align text with other elements.
Customizing the Print Dialog in Windows The most common reason for taking control of the printing process is to add items to the Print dialog. Microsoft provides a set of common dialog boxes, three of which apply to printing.
These three dialog boxes (Print Setup, Page Setup, and Print) get some information from the common dialog DLL (COMDLG32.DLL) and some information from the printer driver. Figure 8.3 shows the common Print dialog.
Figure 8.3 : Microsoft makes it easy to display the common Print dialog.
To customize the Print dialog, use MFC to write your plug-in. Then follow this procedure:
BOOL CPluginView:OnPreparePrinting(CPrintInfo* pInfo)
{
// replace the default CPrintDialog with our own
delete pInfo->m_pPD;
pInfo->m_pPD = new CPluginPrintDialog(FALSE); //
FALSE for Print Dialog
// set up some nice defaults
pInfo->m_pPD->m_pd.nMinPage = 1;
pInfo->m_pPD->m_pd.NMaxPage = 0xffff;
// point the view to our version of the print dialog
pInfo->m_pPD->m_pd.hInstance = AfxGetInstanceHandle();
pInfo->m_pPD->m_pd.lpPrintTemplateName =
MAKEINTRESOURCE(PRINTDLGORD);
// and turn on the template
pInfo->m_pPD->m_pd.Flags |= PD_ENABLEPRINTTEMPLATE;
// finally, call the parent's method to complete the task
return DoPreparePrinting(pInfo);
}
| Tip |
If you don't want some of the controls in the common Print dialog, disable them. Don't delete them-CPrintDialog expects to find them when it calls DoDataExchange(). You can hide them, but users may be confused if you make too many changes to the standard dialog. You can communicate your interface design more clearly by leaving the control present but disabled. |
Customizing the Print
Dialog on a Macintosh If
your plug-in is designed for the Macintosh, you can add items
to the standard Print
dialog. Delete no items from the standard
dialog or change their position. (Not only can these kinds of
changes confuse the software, they also can confuse the user.)
| Caution |
Don't use more than half the screen for your custom controls. Apple warns that future versions of their standard Print dialog may use as much as half the screen, leaving only the bottom half for your customization. Remember, too, that many users still have older Macs with 9-inch screens. As a practical matter, if your plug-in needs so many controls that it takes half the screen (even a 9-inch screen), it's probably too busy to be usable. Consider moving some of these controls to a separate dialog, and hooking this dialog to a pop-up menu or another control in your plug-in window. |
If you want to understand how to change the Print dialog, review how the standard dialog works. Your full-page plug-in puts up a Print dialog by calling PrJobDialog(). This function calls the printer driver of the currently active printer. The printer driver, in turn, calls PrDlgMain(). PrDlgMain() has the following specification:
Boolean PrDlgMain(THPrint hPrtRec, ProcPtr pDlgInit)
The ProcPtr has the address of the dialog initialization procedure-for the Print dialog. This procedure defaults to PrJobInit().
PrDlgMain() calls the pDlgInit, which sets up the dialog, dialog hook, and dialog event filter. Then PrDlgMain() calls ShowWindow() and ModalDialog() to make the dialog available to the user. As the user interacts with the controls, events are passed to the dialog event filter.
Figure 8.4 illustrates this calling sequence.
To add controls to the standard Print dialog, use a resource editor such as ResEdit to make a new Dialog Item List (DITL) with the controls you want to add. Write your own dialog initialization routine, which appends the items from your DITL to the standard dialog and initializes those items. For example, you might write the following:
const short kPluginDITL=256;
.
.
.
pascal TPPrDlg PluginJobDialog( THPrint thehPrint)
{
// Append the items in kPluginDITL to the DialogPtr PrtJobDialog
.
.
.
// save the old procedure; we'll need it if the user hits any of
// the standard controls.
prPItemProc = (long)PrtJobDialog->pItemProc;
PrtJobDialog->pItemProc = (ProcPtr)PluginJobItems;
return PrtJobDialog;
}
pascal void PluginJobItems(TPPrDlg theDialog, short theItemNo)
{
// handle hits on our items
// use a switch on the item number
// If the item hit was not ours, call the standard handler
CallPascal(theDialog, theItemNumber, prPItemProc);
}
| Tip |
Symantec offers an electronic reference to the Mac toolbox functions, known as THINK Reference. This product includes a nice function named AppendDITL() which can be called to "Append the items in kPluginDITL to the DialogPtr PrtJobDialog" as required in the preceding code. |
| On the Web |
For more information on THINK Reference, visit Symantec on-line at http://www.symantec.com/. |
Now, when you call PrDlgMain(), pass the address of your dialog initialization function. PrDlgMain()calls your function, which appends items from your DITL to the standard Print dialog.
When any item in the dialog gets a hit, your PluginJobItems() handles the event. If the hit isn't on one of your plug-in's controls, you call the default handler.
When the user finally activates the OK button, ModalDialog returns, and your plug-in can read the value the user set in the custom controls. If the user clicks Cancel, your implementation of NPP_Print() should just clean up and exit.
It's said that "All good things must come to an end ". So it is with plug-ins. When the user leaves the page on which your plug-in content is displayed, Navigator calls your plug-in's NPP_Destroy() function, and then deletes the plug-in instance.
In C++, every class has one or more constructors and one destructor. The constructors get called in response to instantiations. The destructor gets called when a stack-based instance goes out of scope or a dynamically allocated instance is deleted.
If a C++ class allocates memory in its constructor, it should deallocate that memory in the destructor. If you forget to deallocate the memory, the pointer to the allocated memory is lost, but the memory remains unavailable for use by other applications.
If the plug-in runs long enough, it eventually uses up all memory and crashes the Netscape client. Depending upon the operating system, the user may lose work in other open applications as well. The phenomenon of allocating memory and then failing to deallocate it is known as a memory leak, which can lead to subtle defects in your software.
Figure 8.5 illustrates a memory leak.
Recall that you should allocate dynamic memory for a plug-in by calling NPN_MemAlloc(). The corresponding routine to deallocate that memory is NPN_MemFree(). In C++, implement new and delete using NPN_MemAlloc() and NPN_MemFree() to ensure proper behavior. The section, "The Run-Time Model," in Chapter 5 "Design Issues," shows how to use NPN_MemAlloc() and NPN_MemFree() to implement custom versions of new and delete.
Besides deallocating dynamic memory allocated in constructors, you should make sure that you deallocate all memory you have reserved in the structure pointed to by instance->pdata. The rule of thumb to remember is "For every new, there must be a delete."
Often, you want to associate some record of the user's activity with the URL they visited. For example, the user may have opened a video clip and played the first 30 frames. If the user leaves this URL and comes back later, perhaps play should resume at frame 31. Perhaps the user has printed the current document, specifying parameters such as page range and orientation. If he or she returns to this URL and prints the document again, you may want your plug-in to remember the parameters the user previously specified and use these parameters as the default settings.
You can store data in Navigator's memory and associate it with the current URL by saving this data in the save parameter of NPP_Destroy().
NPP_Destroy() is specified as follows:
NPError NPP_Destroy(NPP instance, NPSavedData **save);
Here, NPSavedData is as follows:
typedef struct _NPSavedData
{
int32 len;
void* buf;
} NPSavedData;
When to Use NPP_Destroy() If you allocate saved data in NPP_Destroy(), Navigator keeps this data in case the user returns to the same URL. You might want to save data if the following instances are true:
How to Use NPP_Destroy() To use saved data, follow these five steps in NPP_Destroy():
Now complete NPP_Destroy().
Navigator deletes your plug-in instance. Later, if the user visits
the same URL as the earlier instance, Navigator passes theStoredData
in the saved parameter of
NPP_New().
| Tip |
Make sure that whatever structure you put into (*save)->buf (in this example, TStoredData) is flat-that is, none of its members are themselves dynamically allocated. If Navigator runs short on memory, it begins deallocating saved data to free space. Because it doesn't know the internal structure of your storage class, it cannot deallocate internal dynamic memory. Figures 8.6 and 8.7 illustrate the wrong and right ways to design TStoredData. |
| Caution |
Remember that Navigator deletes saved data if it needs to free up memory. Remember also that all saved data is stored in RAM and is lost if the user exits Navigator. If your plug-in produces critical data that you don't want to lose, either save it on the user's hard disk, or use NPN_PostURL() or NPN_PostURLNotify() to send the data back to the server. Note that some users are uncomfortable with the idea that a program they download from the Net may write to their hard disk. Be courteous-ask the user at runtime if it's okay, or at least put a conspicuous notice in your documentation that the file will be written to the user's hard drive. |
If you are an experienced programmer, you are used to explicitly
closing and deallocating all windows when the program exits. You
don't need to do this in a plug-in. By the time NPP_Destroy()
is called, the window is gone.
| Caution |
Do not do any graphics operations in NPP_Destroy(). Your window is no longer valid-it is gone! |
Recall from Chapter 6 "NPP Methods: Starting the Plug-In," that Navigator calls NPP_Initialize() when the plug-in is first loaded, before calling NPP_New() to make the first instance. NPP_Initialize() is the place to allocate any data that is used by all instances of your plug-in.
NPP_Destroy() corresponds to NPP_New() in that NPP_Destroy() is called whenever the instance is deleted. Similarly, NPP_Shutdown() corresponds to NPP_Initialize().
When the last instance of a plug-in is deleted, Navigator calls NPP_Shutdown() so that it can delete any dynamic data allocated by NPP_Initialize(). Just as in NPP_Destroy(), failure to free all the memory allocated in NPP_Initialize() leads to a memory leak.
During the life of your plug-in, the user may want to print the contents of the plug-in. If the plug-in is embedded, Navigator calls the plug-in and requests that it draw its contents into the window provided.
If the plug-in is full-page, Navigator calls your plug-in and invites it to take over the printing process. You may choose to do so to customize the Print dialog or to access details of the printer driver.
If you choose not to run the printing process from the plug-in, Navigator calls your plug-in back when it is time to draw its content.
When the plug-in's life is over, Navigator calls NPP_Destroy(). In NPP_Destroy() you should deallocate all dynamic memory, including the memory allocated in the data structure pointed to by instance->pdata. To keep any data for use by the plug-in the next time it is called for this URL, you can save this data during NPP_Destroy().
When the last instance of the plug-in is deleted, Navigator calls NPP_Shutdown(), giving your plug-in a chance to free any resources allocated during NPP_Initialize().