Chapter 6 "NPP_Methods: Starting the Plug-In," shows how Navigator loads the plug-in and starts an instance. By the end of that chapter the plug-in has access to its data and has a window open.
Recall that a plug-in, in its simplest form, transforms data from a nonnative MIME data type to some representation inside the Navigator window. Typically this representation is visual-Navigator opens a portion of its window, or even an entire new document, for use by the plug-in.
Less commonly, there is no data-the plug-in interacts with the user rather than with data. On other occasions, the representation may be nonvisual. For example, a plug-in may be used to play a downloaded sound.
Whatever the plug-in's task, in many cases the programmer want to interact with the user as long as the plug-in is active. He or she may put up buttons, sliders, or pop-up menus in the plug-in's window. This chapter shows how to capture user events and make them available in the plug-in.
The HTML page has loaded. Navigator, seeing an <EMBED> tag, is retrieving data from the Web server and has loaded your plug-in. Your plug-in is receiving data and has displayed that data in its assigned window. For many plug-ins, the day is over-their work is done.
For more sophisticated plug-ins, however, tasks still await. You have included user controls in your window, and you must wait while the user clicks your buttons or types in your text fields. In short, you are waiting for an event.
The three major platforms supported by Navigator (Windows, Macintosh, and UNIX) each have different mechanisms for notifying a program of a user action.
Modern operating systems such as Windows NT, Windows 95, and the Macintosh operating system take complete control of the hardware. If the user moves the mouse, it is the operating system that tracks the cursor. If the user presses a key on the keyboard, it is the operating system that receives the character.
Each operating system provides a way for the programmer to register
an interest in certain external events so that the application
is notified by the operating system when these events occur.
| Note |
The discussion in this chapter about Microsoft Windows generally is applicable to all versions of this product. Where the versions differ, this chapter assumes that the Win32 model is used in Windows NT, Windows 95, and the Win32s simulator. (Strictly speaking, Windows 3.1 isn't an operating system but, rather, a sophisticated application that runs on top of MS-DOS.) |
Getting Events-the Windows Way In Windows, all user input is translated by the operating system into messages that are placed in a data structure known as the system queue. The operating system periodically looks in the queue and redirects messages to the queue of the appropriate application. When an application is ready to process an input message, it reads the next message from its queue.
Messages can come directly from the application, from other applications, or from the operating system, as well as from the user (via the hardware). The user, for example, might click the close box of a window, which notifies the operating system to close this window. Before closing the window, the operating system sends the application a "close session" message, which gives the application a chance to finish tasks.
All Windows messages have four parameters, regardless of where they come from or what they are telling your program:
The most common message types are Windows messages. In your documentation
all of these messages begin with the characters, WM_.
| Note |
The Windows message queue has greater complexity than is described in this section. Windows, for example, can insert asynchronous messages from sources like the timer into the queue ahead of the usual synchronous messages. The queue also allows four different levels of message priority. For the purposes of plug-ins, however, it is sufficient to use the simplified model. |
Remember that Netscape's call to NPP_SetWindow() passes a native window handle to the Windows plug-in. The operating system is prepared to dispatch events associated with this window to the plug-in. The plug-in just has to ask for them.
Here's a typical piece of code a plug-in can use to handle messages it receives. The full code appears on the CD-ROM as /source/chap07/msg1.cpp.
Listing 7.1 msg1.cpp
LONG theResult = -1;
switch(Message)
{
case WM_PALLETTECHANGED:
{
.
.
.
break;
}
case WM_PAINT:
{
.
.
.
break;
}
default:
{
// this message was not for us; call old Window process
.
.
.
break;
}
}
return theResult;
If the WM_PALLETTECHANGED message or the WM_PAINT message ever reaches the plug-in, the plug-in now knows how to handle it. The question is, "How does the plug-in tell Windows that it wants these messages?"
The answer is a callback function. Every window in the Windows operating system has a list of functions, known as WindowProc functions, attached to it. A typical callback function contains the message-handling code just described.
Note that in the message-handling code fragment, the handler for default case is missing. Windows expects a WindowProc to maintain a pointer to any WindowProc it replaces so that if the new WindowProc cannot handle a message, it can forward it to its predecessor.
Windows makes it easy to reserve space for these pointers in their
function, RegisterClass.
Unfortunately, Navigator doesn't request this extra space, so
plug-ins for Navigator 3.0 on Windows also must maintain a linked
list.
| On the Web |
The code to implement this list is given in the nspi30 SDK (which is available on-line at http://home.netscape.com/eng/mozilla/3.0/handbook/plugins/index.html). |
Figure 7.1 illustrates the process by which Windows dispatches messages.
To install a new callback function, use the SetWindowLong() function, which you can use to change many different parameters of the window. For example, if This is a pointer to the current data structure that you stored in pData, you can put the following code in your implementation of NPP_SetWindow():
This->hWnd = (HWND)(DWORD)This->fWindow->window;
This->lpfnOldWndProc =
(FARPROC) SetWindowLong(This->hWnd,
GWL_WNDPROC,
(DWORD)myCallbackFunction);
AssociateInstance(This->hWnd, This);
When NPP_SetWindow() is called,
this code sets the WindowProc
of the window
identified by hWnd to the
callback function myCallbackFunction.
Then it links the old WindowProc,
carefully saved in This->
lpfnOldWndProc, by calling
AssociateInstance().
| Note |
AssociateInstance() is part of the system that maintains a linked list of data structures associated with each of the plug-in's open windows. The code that implements this list is omitted here but is available in the Netscape Plug-In 3.0 SDK (nspi30). |
When a message is sent to the window identified by hWnd, the callback function is invoked. If the message is on the list of messages handled by the switch statement, the appropriate action is taken and the function returns zero. Otherwise, the default case is taken and the old WindowProc tries to handle the message.
Here is a sample of the callback function, using the message handling code shown previously. The full code for this sample is on the CD-ROM, under the name /source/chap07/callback.cpp.
Listing 7.2 callback.cpp
LONG_NP_LOADDS WINAPI myCallbackFunction ( HWND hWnd,
WORD Message,
WORD wParam,
LONG lParam)
{
PluginInstance* This = GetInstance(hWnd);
LONG theResult = -1;
switch(Message)
{
case WM_PALLETTECHANGED:
{
.
.
.
break;
}
case WM_PAINT:
{
.
.
.
break;
}
default:
{
// this message was not for us; call old Window process
theResult = CallWindowProc(This->lpfnOldWndProc,
hWnd,
Message,
wParam,
lParam);
break;
}
}
return theResult;
}
Look in the Microsoft-supplied file winuser.h to see the list of windows messages (WM_s) available. Which messages should your plug-in handle? You should certainly handle WM_PAINT. This message is sent whenever the window needs to be redrawn. It is sent once as the plug-in is starting, and then again whenever one of the following events occurs:
Some of these events, like resizing, trigger NPP_SetWindow(), but play it safe and handle WM_PAINT directly.
You also may want to handle WM_PALETTECHANGED. This message is sent by Windows when a new window gets the keyboard focus. When the focus changes, the window with the focus sets its logical palette, which changes the system palette. The operating system sends WM_PALETTECHANGED to all visible windows, which gives them a chance to set their own logical palette.
Getting Events on a Macintosh Recall from Chapter 6, "NPP Methods: Starting the Plug-In," that Windows and UNIX plug-ins get a native window, but Macintosh plug-ins get a GrafPort. One consequence of this implementation is that the plug-in sees all events, although it may handle very few. Otherwise, the practical effect of this difference is small.
When an event occurs, Netscape calls NPP_HandleEvent(). This function has the specification:
int16 NPP_HandleEvent(NPP instance, void *event);
The second parameter is a pointer to a standard Macintosh EventRecord. Macintosh events are like Windows messages. Event types include the following:
In addition to the usual Macintosh events, Navigator adds three custom events:
You can read the event type from the event->what
field. Generally, your implementation of NPP_HandleEvent()
should return TRUE if your
plug-in handles the event and FALSE
if it does not.
| Tip |
The definitive guide for Macintosh programming is Inside Macintosh (Addison-Wesley, 1994). The first edition of Inside Macintosh was organized chronologically. For example, Volume I dealt with the earliest versions of the operating system and Volume VI, published in 1991, dealt with System 7.0. The second edition is has a topical organization and is much easier to use. For day-to-day use you want the volumes, Overview, Macintosh Toolbox Essentials, and More Macintosh Toolbox. Then you want selected volumes, depending on what your plug-in does. For example, you may need the volume on QuickTime to play video clips in this standard. Most of the C/C++ compilers for the Macintosh come with CD-ROMs that contain much of the information in Inside Macintosh. If you prefer a paper copy, be sure to get the appropriate volumes of Inside Macintosh-otherwise, you may decide that you are perfectly satisfied with the CD-ROM documentation. |
| Caution |
The events described in this section are known as low-level events in the Apple documentation. Do not confuse these events with Apple Events, which are high-level messages between applications. Apple Events correspond to Microsoft Windows's Object Linking and Embedding (OLE) automation, in which one application can send messages to another, asking it to provide data or services. OLE is described in greater detail in Chapter 14, "If All the World Used Microsoft...." |
Typical code to implement NPP_HandleEvent() might include the following lines:
Bool theResult = FALSE;
switch (theEvent.what)
{
case mouseDown:
{
Handle_Mouse_Down( &theEvent );
theResult = TRUE;
break;
}
case updateEvt:
{
BeginUpdate( window );
EraseRgn( window->visRgn );
Update( window );
EndUpdate( window );
theResult = TRUE;
break;
}
}
return theResult;
Which Macintosh events should your application handle? The updateEvt is analogous to the Windows message WM_PAINT, and should be handled in a similar fashion. Handle other events as needed by your plug-in.
What Happens in X? The event model in Xlib is similar to the one used by Windows and the Macintosh. Remember that in the X Window System, the X server is associated with the user's workstation and the client may be on a separate processor. Nevertheless, X makes this distribution transparent to the programmer.
You can still think in terms of the server sending events to the client to tell the client that its window needs to be updated. (X calls this event Expose.)
Your plug-in should certainly handle Expose, just as the Windows plug-in handles WM_PAINT and the Macintosh plug-in handles updateEvt. Recall that X widgets are implemented as windows in their own right-don't forget to forward events received by your plug-in to any off-the-Net or commercial widgets your plug-in uses.
Microsoft Windows, the X Window System, and the Macintosh OS each generate a variety of events or messages associated with the keyboard. This section shows how to capture key-down messages in a Windows plug-in.
Recall that Windows messages are handled by a WindowProc function. Your window's WindowProc gets no keyboard messages, however, unless you assign it the keyboard focus.
To give your plug-in window the keyboard focus, call SetFocus(), passing it the HWND to your window. A good place to add this line is at the bottom of your NPP_SetWindow() implementation so that your window is given the focus when it is first instantiated, as well as after any resizing or repainting.
Here's a fragment of the revised NPP_SetWindow():
NPError NP_LOADDS NPP_SetWindow(NPP instance, NPWindow* window)
{
.
.
.
This->hWnd = (HWND)(DWORD)This->fWindow->window;
.
.
.
// give our plug-in keyboard focus
SetFocus (This->hWnd);
return NPERR_NO_ERROR;
}
Next, add code to the window's callback function to handle the keyboard messages. Here's a portion of a WindowProc function to handle the Windows message WM_KEYDOWN:
LONG NP_LOADDS WINAPI SubClassFunc( HWND hWnd,
WORD Message,
WORD wParam,
LONG lParam)
{
PluginInstance* This = GetInstance(hWnd);
int nVirtKey;
LONG lKeyData; // not used in this example
char buffer[2];
switch (Message)
{
.
.
.
case WM_KEYDOWN:
{
// Windows uses "virtual keys" to accommodate non-Roman character
// sets.
// Unicode is at the heart of Window's future.
// When the message is WM_KEYDOWN, wParam holds the virtual key
// and lParam holds extension information about the keypress
nvirtKey = (int) wParam;
lKeyData = lParam;
// put the incoming character into a string
buffer[0] = (char) nvirtKey;
buffer[1] = NULL;
// append the string into the instance's persistent buffer
strcat(This->fBuffer, buffer);
// trigger a repainting of the window
InvalidateRect(hWnd, NULL, FALSE);
UpdateWindow(hWnd);
break;
}
.
.
.
case WM_PAINT
{
PAINTSTRUCT paint;
HDC hDC = BeginPaint(hWnd, &paint);
TextOut(hDC, 0, 0, This->fBuffer, strlen(This->fBuffer));
EndPaint(hWnd, &paint);
break;
}
.
.
.
}
Note that this code collects the keystrokes in a structure named fBuffer, which is a member of This. This is a pointer to an instance of class PluginInstance. Add fBuffer to class PluginInstance by adding the line shown in bold.
typedef struct _PluginInstance
{
NPWindow* fWindow;
HWND hWnd;
uint16 fMode;
#ifdef STRICT
WNDPROC lpfnOldWndProc;
#else
FARPROC lpfnOldWndProc;
#endif
NPSavedData* pSavedInstanceData;
char fBuffer[80];
PluginInstance* pNext;
} PluginInstance;
Finally, initialize the fBuffer member to the empty string. Be careful where you put this initialization. The callback function will be called on every keystroke, so this clearly is not the right place. Likewise, NPP_SetWindow() gets called when the window is resized or redrawn, as well as when the window is initially allocated.
You can put initialization code for instance variables in NPP_New(), or in NPP_SetWindow(), inside the if statement that verifies that the window is new:
NPError NP_LOADDS NPP_SetWindow(NPP instance, NPWindow* window)
{
if (NULL == instance)
return NPERR_INVALID_INSTANCE_ERROR;
PluginInstance* This = (PluginInstance*) instance->pdata;
if ((window->window != NULL) && (This->hWnd == NULL))
{
This->fWindow = window;
This->hWnd = (HWND)(DWORD)This->fWindow->window;
.
.
.
// empty the string
This->fBuffer[0] = NULL;
}
.
.
.
}
Figure 7.2 shows the resulting plug-in in action.
Figure 7.2 : This simple plug-in translates every keystroke into a character in the plug in window.
Several things are wrong with this plug-in, not the least of which is that fBuffer has a fixed length that will quickly overflow. If you want a quick fix to this problem, you might replace the char array with a dynamically sized container from the Standard Template Library (STL). Chapter 2 "A C++ Primer," describes the STL.
The more immediate problem is that the program translates every keystroke to the screen-shift keys, delete keys, and also character keys. A better design adds a switch statement inside the WM_KEYDOWN case to handle noncharacter keys.
With this design, the program shows uppercase characters when the shift key is down. It deletes characters from the buffer when the backspace key is pressed.
This kind of keyboard behavior is so basic, however, that we shouldn't have to build it from scratch. Someone has written a generic keyboard and text buffer manager that we can incorporate into our program.
Microsoft has written classes to handle thousands of common Windows
tasks and provides it as Microsoft Foundation Classes (MFC). Chapter
17, "Using Class Libraries and Frameworks," explores
the general topic of class libraries and MFC in particular.
| Tip |
To implement a more sophisticated text editor in your plug-in, use the CEdit class from MFC. You can get even more functionality by using CEditView, which adds printing and find-and-replace capability. If you need to display text in more than one font or if you need special character formatting, use class CRichEditView. |
Handling mouse clicks is just as simple as handling key presses, but the amount of code is greater because users expect to be able to do more with the mouse.
You don't have to do anything special to have mouse messages sent to your window. Depending on the features you want to implement, however, you may need to handle more than one mouse message. This section shows how to implement a simple "scribbler" that allows the user to draw with the mouse.
The design of the scribbler is based on three mouse messages:
When Windows sends WM_LBUTTONDOWN, the plug-in records the mouse location and records that it is now in the "drawing" state. As the mouse is moved, the plug-in records the new mouse location, and calls InvalidateRect() and UpdateWindow() to force a WM_PAINT message.
The WM_PAINT handler draws a line from the old mouse location to the new mouse location if the plug-in is in the drawing state. When, finally, the user releases the left mouse button, the plug-in records the fact that it no longer is in the drawing state.
Here are the additions to the callback function to handle these three new messages, and the additional code for WM_PAINT:
LONG NP_LOADDS WINAPI SubClassFunc( HWND hWnd,
WORD Message,
WORD wParam,
LONG lParam)
{
PluginInstance* This = GetInstance(hWnd);
switch (message)
{
.
.
.
case WM_LBUTTONDOWN:
{
// Windows encodes the mouse position in the lParam parameter.
This->newMouseLocation.x = LOWORD(lParam);
This->newMouseLocation.y = HIWORD(lParam);
This->oldMouseLocation = This->newMouseLocation;
SetCapture(hWnd);
This->bDrawTrail = TRUE;
break;
}
case WM_MOUSEMOVE:
{
if (This->bDrawTrail)
{
This->oldMouseLocation = This->newMouseLocation;
This->newMouseLocation.x = LOWORD(lParam);
This->newMouseLocation.y = HIWORD(lParam);
InvalidateRect(hWnd, NULL, FALSE);
UpdateWindow(hWnd);
}
break;
}
case WM_LBUTTONUP:
{
ReleaseCapture();
This->bDrawTrail = FALSE;
break;
}
.
.
.
case WM_PAINT:
{
HPEN hOldPen;
HPEN hPen;
long colorShade = 0x000001L;
int penWidth = 2;
PAINTSTRUCT paint;
HDC hDC = BeginPaint(hWnd, &paint);
hPen = CreatePen(PS_SOLID, penWidth, colorShade);
hOldPen = (HPEN) SelectObject(hDC, hPen);
MoveToEx(hDC,
This->oldMouseLocation.x,
This->oldMouseLocation.y,
NULL);
LineTo ( hDC,
newMouseLocation.x,
newMouseLocation.y);
SelectObject(hDC, hOldPen);
DeleteObject(hPen);
EndPaint(hWnd, &paint);
break;
}
.
.
.
} // end switch
.
.
.
return 0L;
}
In this code fragment, three pieces of data must be preserved between invocations of the plug-in: oldMouseLocation, newMouseLocation, and bDrawTrail. Add these definitions to pluginInstance:
typedef struct _PluginInstance
{
NPWindow* fWindow;
HWND hWnd;
uint16 fMode;
#ifdef STRICT
WNDPROC lpfnOldWndProc;
#else
FARPROC lpfnOldWndProc;
#endif
NPSavedData* pSavedInstanceData;
POINT oldMouseLocation;
POINT newMouseLocation;
BOOL bDrawTrail;
PluginInstance* pNext;
} PluginInstance;
Note that this plug-in uses SetCapture() and ReleaseCapture() to ensure that all mouse movement is sent to this plug-in while the left mouse button is down, even if the mouse moves beyond the bounds of the plug-in's window.
Figure 7.3 shows the scribbler in action.
As the plug-in becomes more sophisticated, the callback function becomes larger and more complex. You'll quickly want to pull the more elaborate handlers (such as WM_PAINT) into their own functions (such as onPaint).
Then, to handle a new message, you need to be sure to make two changes: add the new message to the switch statement in the callback function, where you call the handler, and implement the handler function itself.
Whenever you have to keep two things synchronized in software, you have a potential defect. Sometimes you struggle over a program in which you implemented a handler function but forgot to add the message in the callback function. At other times, the linker complains that you didn't supply a handler, even though you called one.
If you are using Microsoft Visual C++ or a similar compiler that supports Microsoft Foundation Classes, you can get the development environment to help you map messages onto their handlers, using a construct known as a message map.
Most MFC applications include the macro DECLARE_MESSAGE_MAP(); in their window definition. This macro works much like the virtual keyword in C++-it tells the compiler that the class overrides the handling of certain messages.
To complete the hookup of the message map, add the BEGIN_MESSAGE_MAP() macro to the body of the application, listing the messages your application is prepared to handle. To handle the WM_PAINT message, for example, your message map would include the following line:
ON_WM_PAINT()
The default name of the handler function for WM_PAINT is OnPaint(). When a plug-in with ON_WM_PAINT() in its message map receives the message WM_PAINT, OnPaint() is invoked. From here, processing goes on just as it did when you handled WM_PAINT explicitly in your WindowProc.
If you use Microsoft's Visual C++, the fastest way to set up a
plug-in based on MFC is to use the MFC AppWizard. Open the Microsoft
Developer Studio and choose File, New. In the New
dialog, choose Project Workspace, and in the New Project Workspace
dialog choose MFC AppWizard (dll). Fill in a name and click the
Create button.
| Caution |
Remember that under Windows, plug-ins need a name that begins with the letters "np". You will find it's difficult to change a project's name after it is set up. To keep your project internally consistent, put the "np" characters in the name right from the beginning-in the Name field of the New Project Workspace dialog. |
The MFC AppWizard for dynamic Link libraries (DLLs) has only one
step, and the default values give reasonable behavior for plug-ins.
Click the Finish button, then click OK to approve the New Project
Information.
| Tip |
The Professional edition of Microsoft Visual C++ gives you the option of statically linking MFC into your DLL. This option is worth serious consideration. If you statically link MFC in, your plug-in will be bigger but you don't have to worry that the end-user doesn't have the MFC DLL. If you dynamically link MFC, you save space but, undoubtedly, some users will not have the MFC DLL on their computer. They won't be able to run your plug-in without it. |
Examine the application class produced by the AppWizard. If you named your plug-in npTest, this class is named CNpTestApp. At the bottom of the class declaration you find the following macro:
DECLARE_MESSAGE_MAP()
In the .cpp file where the class methods are implemented you find the following lines:
BEGIN_MESSAGE_MAP(CNpTestApp, CWinApp)
//{{AFX_MSG_MAP(CNpTestApp)
//NOTE - the ClassWizard will add and remove mapping macros
//DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
When AppWizard first builds your class, it reserves a message map but doesn't put messages into it. The comments have a special format-they are maintained by tools in the Developer Studio.
The macro BEGIN_MESSAGE_MAP() takes two parameters. The first (CNpTestApp in our example) is the target of the messages associated with this map. Any class derived from MFC's CCmdTarget can have a message map.
Note that CNpTestApp is derived from CWinApp. In MFC, the class CWinApp (or its descendent) provides the main event loop for the programmer. When the class's member function Run is invoked, the main event loop begins to spin, dispatching messages (or calling the idle function when there is nothing else to do).
Run dispatches messages to the application's windows. Each window has a WindowProc defined at the class level. A message map in that window class dispatches messages to their handlers.
Figure 7.4 shows the message-handling hierarchy of MFC. If your derived class gets a message it doesn't understand, it forwards the message up the hierarchy. The second parameter of the BEGIN_MESSAGE_MAP() macro in a given class implementation contains the name of the class directly "above" the current class. This class is the point at which your code interacts with MFC code to handle messages.
Figure 7.4 : The message map for this example would be BEGIN_MESSAGE MAP (CNpTestApp, CWinApp).
Each message macro (such as ON_WM_PAINT) is associated with a handler, such as OnPaint(). Note that the macros don't take arguments. The framework passes arguments to the handler if it needs them. Examine the MFC declaration of class CWnd. You will find the handler for ON_WM_PAINT declared as follows:
afx_msg void OnPaint();
The afx_msg keyword is a
flag to the preprocessor, telling it that this function is associated
with a message map. Its presence reminds the programmer that this
function is special, in much the same way as the virtual
keyword reminds a programmer about the special handling of virtual
functions. (Message maps are not implemented using the
virtual mechanism, although the effect is similar.)
| Tip |
Can't find OnPaint() in the example MFC-based plug-ins? Sophisticated applications benefit by having the data separated from the presentation of that data. In MFC, the data is stored in a document and presented by one or more views. Views are ultimately derived from the MFC class CView, which handles WM_PAINT by calling the view's OnDraw() member. You as the programmer are expected to implement OnDraw(). So when your MFC-based plug-in gets the WM_PAINT message, your window's message map forwards it up the hierarchy until it reaches Cview. Cview calls the OnDraw() member back in your own derived class. |
So far, these examples have ignored the major reason for building a plug-in-a server is sending you a stream of data that Netscape doesn't know how to handle. Recall from Chapter 6 "NPP Methods: Starting the Plug-In," that the preferred way to read your data is as a stream (stype is NP_NORMAL).
If you accept the stream in NP_NORMAL stype, Navigator makes a series of NPP_WriteReady()-NPP_Write() calls until the stream is completely sent. If you're developing your own data type, consider placing information about the size of the file at the head of the file so that you can receive it as a stream.
Here's a procedure for handling many types of streaming data:
Suppose that you define a data type that consists of positive numbers separated by new lines. Following the procedure given in the previous section, you might write the code described in the following sections.
short fBuffer[kBufferSize]; short fTopOfWindow; short fBottomOfWindow;
Set kBufferSize to a reasonable size, with a line like the following:
const short kBufferSize = 32767;
*stype = NP_NORMAL
int32 NP_LOADDS NPP_WriteReady(NPP instance, NPStream* stream)
{
int32 theResult = 0L;
if (instance != NULL)
{
PluginInstance* This = (PluginInstance*) instance->pdata;
theResult = (kfBufferSize - (This->fBottomOfWindow) - 1);
}
return theResult;
}
int32 NP_LOADDS NPP_Write(NPP instance, NPStream* stream,
int32 offset, int32 len, void* buffer)
{
if (instance != NULL)
{
PluginInstance* This = (PluginInstance*) instance->pdata;
int i;
// Start stripping off ASCII numbers
char* theString = strtok((char*) buffer, "\n");
if (NULL != theString)
{
This->fBuffer[offset] = atoi(theString);
for (i=offset+1; i<kBufferSize && (i-offset < len); I++)
{
theString = strtok(NULL, "\n");
// keep reading until we run out of room or data
// don't trust len; it can overreport the data
if (NULL == theString)
break;
This->fBuffer[i] = atoi(theString);
}
This->fBottomOfWindow = i;
return (len);
}
else
return 0L;
}
else
return 0L;
}
Writing the Data to the Window Finally, every time the window needs redrawing, plot out the data. This display routine is quite simple-a more sophisticated routine can include scroll bars, autoscaling, grid lines and axes, or even simple animation.
LONG NP_LOADDS WINAPI SubClassFunc( HWND hWnd,
WORD Message,
WORD wParam,
LONG lParam)
{
PluginInstance* This = GetInstance(hWnd);
LONG theResult = 0L;
switch (Message)
{
.
.
.
case WM_PAINT:
{
HPEN hOldPen;
HPEN hPen;
long colorShade = 0x000001L;
int penWidth = 2;
PAINTSTRUCT paint;
HDC hDC = BeginPaint(hWnd, &paint);
hPen = CreatePen(PS_SOLID, penWidth, colorShade);
hOldPen = (HPEN) SelectObject(hDC, hPen);
MoveToEx(hDC, This->fBuffer(This->fTopOfWindow),
This->fTopOfWindow,
NULL);
for (int i=This->fTopOfWindow+1;
i<This->fBottomOfWindow;
i++)
LineTo(hDC, This->fBuffer[i], i); // autoscale here if
SelectObject(hDC, hOldPen);
DeleteObject(hPen);
EndPaint(hWnd, &paint);
theResult = 0L;
break;
} // end case
.
.
.
} // end switch
fTopofWindow=0;
fBottomofWindow=0; return theResult;
}
The Result Figure 7.5 shows the result plotted by the above plug-in.
If you are implementing in MFC, consider deriving a plug-in document that is instantiated in NPP_New(). Attach a view to this document in NPP_SetWindow() and implement that view's OnDraw() member so that it puts a representation of the document into the window.
The default memory map take cares of sending WM_PAINT to CView. CView, in turn, calls its implementation of OnPaint(), which calls your derived view's OnDraw().
During its interaction, your plug-in may produce data that should be shown to the user. Use the Navigator method NPN_NewStream() to send this data to a target in Navigator. Chapter 9 "Understanding NPN Methods," describes the various targets that you can choose.
As a result of the interaction, your plug-in can identify data that is still on the Web and that the user should see. For example, you can include buttons in your plug-in that function like links. If the user clicks one of these links, you want to call for the data from the associated URL.
Use NPN_GetURL() or, for notification, NPN_GetURLNotify() to open a new stream from the Web to Navigator. Note that this stream comes to Navigator, not to the plug-in (unless the content type matches one of the plug-in's content).
If you request the "Notify" version, Navigator calls your NPP_URLNotify() function when the URL transfer completes and reports whether the transfer was successful, unsuccessful, or was stopped by the user.
Finally, you can send data back to the Web by using NPN_PostURL() or NPN_PostURLNotify(). Like NPN_GetURLNotify(), Navigator uses your plug-in's NPP_URLNotify() function to report completion of the request. Again, see Chapter 9, "Understanding NPN Methods," for more detailed information on the NPN_ functions.
After your plug-in is started, you may want to allow the user to interact with the data. At a minimum, you need to handle update events and messages such as WM_PAINT. Combine the user interaction with the NPP_WriteReady()-NPP_Write() loop, which delivers data from the server to your plug-in.
Also: