home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Using the Internet
/
21003.iso
/
email
/
PEGASUS
/
WINPM222.ZIP
/
WINPMAIL.ZIP
/
FORMS
/
MAILMRGE
/
MAILMRGE.C
next >
Wrap
C/C++ Source or Header
|
1995-11-03
|
18KB
|
572 lines
//
// MAILMRGE.C
// Pegasus Mail for Windows extension providing a simple
// "mail merge" capability.
//
// Copyright (c) 1994-95, David Harris, All Rights Reserved.
//
// The author grants explicit permission for this source code to be
// used or modified as required, subject only to the conditions that
// the copyright notices above are preserved and that by using this
// code you agree that the code is provided without warranty of any
// kind, either explicit or implied, and you use it at your own
// risk.
//
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <bwcc.h>
#include <stdio.h>
#include <string.h>
#include <io.h> // for "access()"
#include "..\wpmforms.h"
#include "mailmrge.h"
int atoi (const char *buf);
#ifndef MAXFPATH
#define MAXFPATH 128
#endif
char helpname [MAXFPATH];
char help_used = 0;
// Form dialogs and callbacks: a form DLL can export a function
// which WinPMail's form manager will call with Windows messages
// destined for the form's dialog or window (such as menu selections
// and so forth). The function takes the following form:
typedef long pascal far (*FORM_CALLBACK) (HWND hWnd, WORD wMsg,
WPARAM wParam, LPARAM lParam);
// The return from a form callback is passed back through the
// Windows call chain if non-zero, otherwise the default window
// proc is called. The reasons we call this function instead of
// simply sending messages to the dialog itself are:
//
// 1: Dialogs can only return Boolean values, whereas the callback
// can provide the long value Windows expects to pass back
// through the calling chain.
//
// 2: Having a separate callback means that it is potentially
// possible to have a form which does not create a dialog.
//
//
// Minimum interface: the minimum interface a form DLL must provide
// is a routine called "forminit" which is exported by name. "forminit"
// has the following prototype:
//
// WORD FAR PASCAL _export FORMINIT (WORD version, int variant, HWND hParent,
// char *data, HWND *hDialog, char *callback_name);
//
// "version" is passed in with the version of the WinPMail forms
// manager which is running.
// "variant" indicates what type of form is required - the following
// values are currently defined:
// 0: Create a form for composing a message
// 1: Create a form for reading a message
// "hParent" contains the handle of the WinPMail MDI child window
// which is to contain the form.
// "data" contains any string defined as being required for the
// form in the menu interface.
// "hDialog" should be filled in with the window handle of the
// modeless dialog created within the MDI child window.
// "callback_name" (optional) should be filled in with the name of the
// function in the DLL of the exported function to which messages
// should be sent or NULL if there is none. If NULL, messages are
// sent to the dialog returned in "hDialog". You will use an
// indirect callback of this kind when your extension does not
// create a dialog within the enclosing parent window.
//
// When forminit is called, the DLL should register any window
// classes it needs then create the dialog within the MDI parent
// window and size it to the correct size. On return WinPMail will
// resize the parent window to enclose the dialog correctly. The
// DLL should NOT make the dialog visible - WinPMail will do that
// as required.
#define MAXFIELDS 32 // Maximum number of fields per record
char szFormDlgClassName [] = "bordlg_mrg";
char do_logging = 1;
HFONT hLogFont;
HWND last_focus;
int register_form_classes (void);
HINSTANCE hLibInstance; // set in LibMain, used throughout the DLL
#pragma warn -par
BOOL FAR PASCAL _export generic_proc (HWND hDialog, UINT wMsg,
WPARAM wParam, LPARAM lParam)
{
// General purpose dialog procedure - simply exits as soon
// as any button is pressed returning the ID of the button.
BOOL fProcessed = TRUE;
switch (wMsg)
{
case WM_INITDIALOG :
// centre_window (hDialog);
break;
case WM_COMMAND :
if (HIWORD (lParam) != BN_CLICKED) break;
EndDialog (hDialog, wParam);
break;
default:
fProcessed = FALSE;
break;
}
return fProcessed;
}
static void report_error (char *dlg_name)
{
FARPROC iproc;
iproc = MakeProcInstance ((FARPROC) generic_proc, hLibInstance);
DialogBox (hLibInstance, dlg_name, NULL, (DLGPROC) iproc);
FreeProcInstance (iproc);
}
WORD FAR PASCAL _export FORMINIT (WORD version, int variant, HWND hParent,
char *data, HWND *hDialog, char *callback_name)
{
// First, check to see if the version of the form manager is
// one we can work with. This check is pretty arbitrary - you
// should probably assume that you may not work with any version
// where the major version number is higher than the original
// version you targeted.
if (version < 0x102)
report_error ("VER");
// Now check the variant number; for this extension, we only
// provide a COMPOSER format.
if (variant != 0) return 0;
(*hDialog) = CreateDialog (hLibInstance, (LPCSTR) "MERGE", hParent, NULL);
if ((*hDialog) == NULL) return 0;
return 1;
}
void trim_newline (char *str)
{
/* Remove the terminating newline from a string
** if it's present. */
int i;
i = strlen (str) - 1;
while ((i >= 0) && ((str [i] == '\n') || (str [i] == '\r')))
str [i -- ] = '\0';
}
static int dump_the_data (FILE *dest, FILE *src, char **fields, int nfields)
{
// Write the formatted data into the temporary file we've created
// for the message. Fields are inserted into the message by using
// placeholders in the message file: a placeholder consists of a
// tilde character (~), followed by a string of characters, ending
// with another tilde. The following placeholders are recognized:
//
// "1" .. "32" - replaced by the matching field from the data record
// "~" - replaced by a single tilde
int c, state, field;
rewind (src);
state = 0;
while ((c = fgetc (src)) != EOF)
{
switch (state)
{
case 0 : // Normal state
if (c == '~')
state = 1;
else
fputc (c, dest);
break;
case 1 : // Checking a placeholder
if (c == '~')
{
fputc ('~', dest);
state = 0;
}
else
{
if (strchr ("01234567890", c) != NULL)
{
field = c - '0';
state = 2;
}
else
{
fputc ('~', dest);
fputc (c, dest);
state = 0;
}
}
break;
case 2 :
if (c == '~')
{
if ((field > 0) && (field <= nfields))
fprintf (dest, "%s", fields [field - 1]);
state = 0;
}
else if (strchr ("01234567890", c) != NULL)
{
field *= 10;
field += (c - '0');
}
else
{
fputc ('~', dest);
fputc (c, dest);
state = 0;
}
break;
}
}
return 1;
}
static int mailmerge (HWND hWnd)
{
// Perform the actual mail merge:
//
// * Retrieve and validate the parameters in the dialog
// * Open the data and format files
// * Read the data one line at a time and parse it
// * Generate a message per record using the format file.
//
// Returns: 1 on success
// 0 on failure
char buffer [1024], dfname [MAXFPATH],
ffname [MAXFPATH], mfname [MAXFPATH], seps [80];
char *fields [MAXFIELDS], *s;
FILE *dfil, *ffil, *mfil;
HWND hParent = GetParent (hWnd);
int email_field, i;
// First, check to see that the user has actually defined
// a field separator of some kind.
if (IsDlgButtonChecked (hWnd, IDC_TAB))
{
seps [0] = '\t';
seps [1] = '\0';
}
else
GetDlgItemText (hWnd, IDC_OTHEREDIT, seps, sizeof (seps));
if (seps [0] == '\0')
{
report_error ("SEPS");
return 0;
}
// Now get the data filename and the format filename,
// verify that they exist, then open them.
GetDlgItemText (hWnd, IDC_DATAFILE, dfname, sizeof (dfname));
if (access (dfname, 0) != 0)
{
report_error ("NSDF");
return 0;
}
GetDlgItemText (hWnd, IDC_FORMATFILE, ffname, sizeof (ffname));
if (access (ffname, 0) != 0)
{
report_error ("NSFF");
return 0;
}
if ((dfil = fopen (dfname, "rt")) == NULL)
{
report_error ("NSDF");
return 0;
}
if ((ffil = fopen (ffname, "rt")) == NULL)
{
fclose (dfil);
report_error ("NSFF");
return 0;
}
// Now tell the Extension Manager to create a message for us...
if (SendMessage (hParent, WM_F_NEWMESSAGE, 0, 0) == 0)
{
report_error ("MSGF");
return 0;
}
// ... and fill out the basic fields in the message.
GetDlgItemText (hWnd, IDC_SUBJECT, buffer, sizeof (buffer));
SendMessage (hParent, WM_F_SUBJECT, 0, (LPARAM) buffer);
if (IsDlgButtonChecked (hWnd, IDC_COPYSELF))
SendMessage (hParent, WM_F_COPYSELF, 1, 0);
if (IsDlgButtonChecked (hWnd, IDC_URGENT))
SendMessage (hParent, WM_F_URGENT, 1, 0);
if (IsDlgButtonChecked (hWnd, IDC_CONFIRMREADING))
SendMessage (hParent, WM_F_CONFIRMREADING, 1, 0);
if (IsDlgButtonChecked (hWnd, IDC_USEMIME))
SendMessage (hParent, WM_F_MIME, 1, 0);
GetDlgItemText (hWnd, IDC_EMAIL, buffer, sizeof (buffer));
email_field = atoi (buffer);
if (email_field < 1) email_field = 1;
-- email_field;
// Now we're down to business: we parse the data file one
// line at a time, creating a temporary file for each message
// we create, then send it.
while (fgets (buffer, sizeof (buffer), dfil) != NULL)
{
trim_newline (buffer);
if (buffer [0] == '\0') continue; // Blank line
i = 1;
s = buffer;
fields [0] = buffer;
while (*s)
{
if (strchr (seps, *s) != NULL)
{
*s = '\0';
if (i < MAXFIELDS)
fields [i ++] = s + 1;
}
++ s;
}
fields [i] = NULL;
SendMessage (hParent, WM_F_TEMPFILE, sizeof (mfname), (LPARAM) mfname);
if ((mfil = fopen (mfname, "wt")) == NULL)
{
fclose (dfil);
fclose (ffil);
report_error ("TMPF");
return 0;
}
dump_the_data (mfil, ffil, fields, i);
fclose (mfil);
SendMessage (hParent, WM_F_TO, 0, (LPARAM) (fields [email_field]));
SendMessage (hParent, WM_F_MESSAGEFILE, 0, (LPARAM) mfname);
SendMessage (hParent, WM_F_SENDMESSAGE, 0, 0);
}
fclose (dfil);
fclose (ffil);
return 1;
}
#pragma warn -use
LONG FAR PASCAL _export MrgProc (HWND hWnd, UINT wMsg,
WPARAM wParam, LPARAM lParam)
{
// Service routine for the form's enclosed dialog. This is a
// standard windows modeless WndProc.
DWORD dwResult = 0;
BOOL fCallDefProc = TRUE;
char buffer [256], *s, *to, *subj;
RECT r;
switch (wMsg)
{
case WM_FM_INIT :
EnableWindow (GetDlgItem (hWnd, IDC_OTHEREDIT), FALSE);
CheckRadioButton (hWnd, IDC_TAB, IDC_OTHER, IDC_TAB);
// Now construct our help file name
GetModuleFileName (hLibInstance, buffer, sizeof (buffer));
s = strrchr (buffer, '.');
if (s != NULL)
{
strcpy (s, ".HLP");
strcpy (helpname, buffer);
}
break;
case WM_DESTROY :
// A good place to store some preferences, perhaps.
if (help_used) WinHelp (hWnd, helpname, HELP_QUIT, 0);
break;
case WM_FM_INITFOCUS :
SetFocus (GetDlgItem (hWnd, 101));
break;
case WM_FM_RESTOREFOCUS :
// There's a glitch in the way the Windows MDI manager
// seems to manage focus which means that we can have
// focus taken away and not returned when the user does
// anything outside our window. WinPMail has some quite
// complex logic to deal with this case and will send
// this message whenever focus should be restored in
// our window. We set focus to the last active control
// (which we know from trapping EN_SETFOCUS messages).
if (last_focus) SetFocus (last_focus);
break;
case WM_COMMAND :
fCallDefProc = FALSE;
if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_SETFOCUS)
{
// We have to trap EN_SETFOCUS messages so we know which
// control was active last. When a menu selection is made
// our current control will lose focus and because of a
// curiosity in Windows' MDI management, we won't get it
// back. Instead, WinPMail generates a WM_FM_RESTOREFOCUS
// message which signals to us that we should set focus to
// whatever the last active control was.
last_focus = (HWND) LOWORD (lParam);
break;
}
switch (GET_WM_COMMAND_ID(wParam, lParam))
{
case IDC_TAB :
EnableWindow (GetDlgItem (hWnd, IDC_OTHEREDIT), FALSE);
CheckRadioButton (hWnd, IDC_TAB, IDC_OTHER, IDC_TAB);
break;
case IDC_OTHER :
EnableWindow (GetDlgItem (hWnd, IDC_OTHEREDIT), TRUE);
CheckRadioButton (hWnd, IDC_TAB, IDC_OTHER, IDC_OTHER);
break;
case IDC_BROWSEDATAFILE :
if (SendMessage (GetParent (hWnd), WM_F_BROWSEFILE, 1, (LPARAM) buffer))
{
SendDlgItemMessage (hWnd, IDC_DATAFILE, EM_SETSEL,
0, (LPARAM) 0x7FFF0000L);
SendDlgItemMessage (hWnd, IDC_DATAFILE, EM_REPLACESEL,
0, (LPARAM) buffer);
}
break;
case IDC_BROWSEFORMATFILE :
if (SendMessage (GetParent (hWnd), WM_F_BROWSEFILE, 1, (LPARAM) buffer))
{
SendDlgItemMessage (hWnd, IDC_FORMATFILE, EM_SETSEL,
0, (LPARAM) 0x7FFF0000L);
SendDlgItemMessage (hWnd, IDC_FORMATFILE, EM_REPLACESEL,
0, (LPARAM) buffer);
}
break;
case IDC_HELP :
if (helpname [0])
{
help_used = 1;
WinHelp (hWnd, helpname, HELP_CONTEXT, 1);
}
else
MessageBeep (0);
break;
case IDC_SEND :
if (! mailmerge (hWnd)) break;
// Drop through and close up shop...
case IDC_CANCEL :
PostMessage (GetParent (hWnd), WM_CLOSE, 0, 0);
break;
}
break;
}
if (fCallDefProc)
dwResult = BWCCDefDlgProc (hWnd, wMsg, wParam, lParam);
return dwResult;
}
#pragma warn -sus
void unregister_form_classes (void)
{
// Remove any classes associated with the form; we have the
// same problem here as we do with registering the classes
// for the DLL - we only want to deregister the classes on
// the last time we're unloaded.
if (GetModuleUsage (hLibInstance) > 1) return; // Not a problem
UnregisterClass (szFormDlgClassName, hLibInstance);
}
BOOL FAR PASCAL LibMain (HINSTANCE hInst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine)
{
WNDCLASS wc;
if (! hLibInstance)
{
hLibInstance = hInst;
BWCCGetVersion (); // Forces BWCC to be dynamically loaded.
// Register any window classes used by the form. Forms will usually
// register either one or occasionally two classes which define
// the composition and reader dialogs created by the DLL.
//
// There's a gotcha here, of course (aren't there always, in
// Windows?)... You can't register a window class more than once,
// so if the DLL has already been loaded and the user asks to
// create a second instance of the form, we have to be careful
// not to re-register the class. We do this by checking the
// instance usage counter of the DLL - if it's greater than 1,
// then we DON'T register the classes.
#pragma warn -sig
wc.style = WS_CHILD;
#pragma warn .sig
wc.lpfnWndProc = MrgProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = DLGWINDOWEXTRA;
wc.hInstance = hLibInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szFormDlgClassName;
if (! RegisterClass (&wc))
MessageBeep (0);
}
return (TRUE); // Initialization went OK
}