home *** CD-ROM | disk | FTP | other *** search
/ Using the Internet / 21003.iso / email / PEGASUS / WINPM222.ZIP / WINPMAIL.ZIP / FORMS / MAILMRGE / MAILMRGE.C next >
C/C++ Source or Header  |  1995-11-03  |  18KB  |  572 lines

  1. //
  2. //  MAILMRGE.C
  3. //  Pegasus Mail for Windows extension providing a simple
  4. //  "mail merge" capability.
  5. //
  6. //  Copyright (c) 1994-95, David Harris, All Rights Reserved.
  7. //
  8. //  The author grants explicit permission for this source code to be
  9. //  used or modified as required, subject only to the conditions that
  10. //  the copyright notices above are preserved and that by using this
  11. //  code you agree that the code is provided without warranty of any
  12. //  kind, either explicit or implied, and you use it at your own
  13. //  risk.
  14. //
  15.  
  16. #define STRICT
  17. #include <windows.h>
  18. #include <windowsx.h>
  19. #include <bwcc.h>
  20. #include <stdio.h>
  21. #include <string.h>
  22. #include <io.h>               //  for "access()"
  23. #include "..\wpmforms.h"
  24. #include "mailmrge.h"
  25.  
  26. int atoi (const char *buf);
  27.  
  28. #ifndef MAXFPATH
  29. #define MAXFPATH 128
  30. #endif
  31.  
  32. char helpname [MAXFPATH];
  33. char help_used = 0;
  34.  
  35. //  Form dialogs and callbacks: a form DLL can export a function
  36. //  which WinPMail's form manager will call with Windows messages
  37. //  destined for the form's dialog or window (such as menu selections
  38. //  and so forth). The function takes the following form:
  39.  
  40. typedef long pascal far (*FORM_CALLBACK) (HWND hWnd, WORD wMsg,
  41.    WPARAM wParam, LPARAM lParam);
  42.  
  43. //  The return from a form callback is passed back through the
  44. //  Windows call chain if non-zero, otherwise the default window
  45. //  proc is called. The reasons we call this function instead of
  46. //  simply sending messages to the dialog itself are:
  47. //
  48. //   1: Dialogs can only return Boolean values, whereas the callback
  49. //      can provide the long value Windows expects to pass back
  50. //      through the calling chain.
  51. //
  52. //   2: Having a separate callback means that it is potentially
  53. //      possible to have a form which does not create a dialog.
  54. //
  55. //
  56. //  Minimum interface: the minimum interface a form DLL must provide
  57. //  is a routine called "forminit" which is exported by name. "forminit"
  58. //  has the following prototype:
  59. //
  60. //  WORD FAR PASCAL _export FORMINIT (WORD version, int variant, HWND hParent,
  61. //      char *data, HWND *hDialog, char *callback_name);
  62. //
  63. //  "version" is passed in with the version of the WinPMail forms
  64. //     manager which is running.
  65. //  "variant" indicates what type of form is required - the following
  66. //     values are currently defined:
  67. //       0: Create a form for composing a message
  68. //       1: Create a form for reading a message
  69. //  "hParent" contains the handle of the WinPMail MDI child window
  70. //     which is to contain the form.
  71. //  "data" contains any string defined as being required for the
  72. //     form in the menu interface.
  73. //  "hDialog" should be filled in with the window handle of the
  74. //     modeless dialog created within the MDI child window.
  75. //  "callback_name" (optional) should be filled in with the name of the
  76. //     function in the DLL of the exported function to which messages
  77. //     should be sent or NULL if there is none. If NULL, messages are
  78. //     sent to the dialog returned in "hDialog". You will use an
  79. //     indirect callback of this kind when your extension does not
  80. //     create a dialog within the enclosing parent window.
  81. //
  82. //  When forminit is called, the DLL should register any window
  83. //  classes it needs then create the dialog within the MDI parent
  84. //  window and size it to the correct size. On return WinPMail will
  85. //  resize the parent window to enclose the dialog correctly. The
  86. //  DLL should NOT make the dialog visible - WinPMail will do that
  87. //  as required.
  88.  
  89. #define MAXFIELDS 32    //  Maximum number of fields per record
  90.  
  91. char szFormDlgClassName [] = "bordlg_mrg";
  92. char do_logging = 1;
  93. HFONT hLogFont;
  94. HWND last_focus;
  95.  
  96. int register_form_classes (void);
  97.  
  98. HINSTANCE  hLibInstance;            // set in LibMain, used throughout the DLL
  99.  
  100.  
  101. #pragma warn -par
  102.  
  103.  
  104. BOOL FAR PASCAL _export generic_proc (HWND hDialog, UINT wMsg, 
  105.    WPARAM wParam, LPARAM lParam)
  106.    {
  107.    //  General purpose dialog procedure - simply exits as soon
  108.    //  as any button is pressed returning the ID of the button.
  109.  
  110.    BOOL fProcessed = TRUE;
  111.  
  112.    switch (wMsg)
  113.       {
  114.       case WM_INITDIALOG :
  115. //         centre_window (hDialog);
  116.          break;
  117.  
  118.       case WM_COMMAND :
  119.          if (HIWORD (lParam) != BN_CLICKED) break;
  120.          EndDialog (hDialog, wParam);
  121.          break;
  122.  
  123.       default:
  124.          fProcessed = FALSE;
  125.          break;
  126.       }
  127.  
  128.    return fProcessed;
  129.    }
  130.  
  131.  
  132. static void report_error (char *dlg_name)
  133.    {
  134.    FARPROC iproc;
  135.  
  136.    iproc = MakeProcInstance ((FARPROC) generic_proc, hLibInstance);
  137.    DialogBox (hLibInstance, dlg_name, NULL, (DLGPROC) iproc);
  138.    FreeProcInstance (iproc);
  139.    }
  140.  
  141.  
  142. WORD FAR PASCAL _export FORMINIT (WORD version, int variant, HWND hParent,
  143.    char *data, HWND *hDialog, char *callback_name)
  144.    {
  145.    //  First, check to see if the version of the form manager is
  146.    //  one we can work with. This check is pretty arbitrary - you
  147.    //  should probably assume that you may not work with any version
  148.    //  where the major version number is higher than the original
  149.    //  version you targeted.
  150.  
  151.    if (version < 0x102)
  152.       report_error ("VER");
  153.  
  154.    //  Now check the variant number; for this extension, we only
  155.    //  provide a COMPOSER format.
  156.  
  157.    if (variant != 0) return 0;
  158.  
  159.    (*hDialog) = CreateDialog (hLibInstance, (LPCSTR) "MERGE", hParent, NULL);
  160.    if ((*hDialog) == NULL) return 0;
  161.    return 1;
  162.    }
  163.  
  164.  
  165. void trim_newline (char *str)
  166.    {
  167.    /*  Remove the terminating newline from a string
  168.    **  if it's present. */
  169.  
  170.    int i;
  171.  
  172.    i = strlen (str) - 1;
  173.    while ((i >= 0) && ((str [i] == '\n') || (str [i] == '\r'))) 
  174.       str [i -- ] = '\0';
  175.    }
  176.  
  177.  
  178. static int dump_the_data (FILE *dest, FILE *src, char **fields, int nfields)
  179.    {
  180.    //  Write the formatted data into the temporary file we've created
  181.    //  for the message. Fields are inserted into the message by using
  182.    //  placeholders in the message file: a placeholder consists of a 
  183.    //  tilde character (~), followed by a string of characters, ending
  184.    //  with another tilde. The following placeholders are recognized:
  185.    //
  186.    //  "1" .. "32"  - replaced by the matching field from the data record
  187.    //  "~"          - replaced by a single tilde
  188.  
  189.    int c, state, field;
  190.  
  191.    rewind (src);
  192.    state = 0;
  193.    while ((c = fgetc (src)) != EOF)
  194.       {
  195.       switch (state)
  196.          {
  197.          case 0 :       //  Normal state
  198.             if (c == '~')
  199.                state = 1;
  200.             else
  201.                fputc (c, dest);
  202.             break;
  203.  
  204.          case 1 :       //  Checking a placeholder
  205.             if (c == '~')
  206.                {
  207.                fputc ('~', dest);
  208.                state = 0;
  209.                }
  210.             else
  211.                {
  212.                if (strchr ("01234567890", c) != NULL)
  213.                   {
  214.                   field = c - '0';
  215.                   state = 2;
  216.                   }
  217.                else 
  218.                   {
  219.                   fputc ('~', dest);
  220.                   fputc (c, dest);
  221.                   state = 0;
  222.                   }
  223.                }
  224.             break;
  225.  
  226.          case 2 :
  227.             if (c == '~')
  228.                {
  229.                if ((field > 0) && (field <= nfields))
  230.                   fprintf (dest, "%s", fields [field - 1]);
  231.                state = 0;
  232.                }
  233.             else if (strchr ("01234567890", c) != NULL)
  234.                {
  235.                field *= 10;
  236.                field += (c - '0');
  237.                }
  238.             else 
  239.                {
  240.                fputc ('~', dest);
  241.                fputc (c, dest);
  242.                state = 0;
  243.                }
  244.             break;
  245.          }
  246.       }
  247.  
  248.    return 1;
  249.    }
  250.  
  251.  
  252. static int mailmerge (HWND hWnd)
  253.    {
  254.    //  Perform the actual mail merge:
  255.    //
  256.    //  * Retrieve and validate the parameters in the dialog
  257.    //  * Open the data and format files
  258.    //  * Read the data one line at a time and parse it
  259.    //  * Generate a message per record using the format file.
  260.    //
  261.    //  Returns:  1 on success
  262.    //            0 on failure
  263.  
  264.    char buffer [1024], dfname [MAXFPATH], 
  265.       ffname [MAXFPATH], mfname [MAXFPATH], seps [80];
  266.    char *fields [MAXFIELDS], *s;
  267.    FILE *dfil, *ffil, *mfil;
  268.    HWND hParent = GetParent (hWnd);
  269.    int email_field, i;
  270.  
  271.    //  First, check to see that the user has actually defined
  272.    //  a field separator of some kind.
  273.  
  274.    if (IsDlgButtonChecked (hWnd, IDC_TAB))
  275.       {
  276.       seps [0] = '\t';
  277.       seps [1] = '\0';
  278.       }
  279.    else
  280.       GetDlgItemText (hWnd, IDC_OTHEREDIT, seps, sizeof (seps));
  281.  
  282.    if (seps [0] == '\0')
  283.       {
  284.       report_error ("SEPS");
  285.       return 0;
  286.       }
  287.  
  288.    //  Now get the data filename and the format filename,
  289.    //  verify that they exist, then open them.
  290.  
  291.    GetDlgItemText (hWnd, IDC_DATAFILE, dfname, sizeof (dfname));
  292.    if (access (dfname, 0) != 0)
  293.       {
  294.       report_error ("NSDF");
  295.       return 0;
  296.       }
  297.  
  298.    GetDlgItemText (hWnd, IDC_FORMATFILE, ffname, sizeof (ffname));
  299.    if (access (ffname, 0) != 0)
  300.       {
  301.       report_error ("NSFF");
  302.       return 0;
  303.       }
  304.  
  305.    if ((dfil = fopen (dfname, "rt")) == NULL)
  306.       {
  307.       report_error ("NSDF");
  308.       return 0;
  309.       }
  310.  
  311.    if ((ffil = fopen (ffname, "rt")) == NULL)
  312.       {
  313.       fclose (dfil);
  314.       report_error ("NSFF");
  315.       return 0;
  316.       }
  317.  
  318.    //  Now tell the Extension Manager to create a message for us...
  319.  
  320.    if (SendMessage (hParent, WM_F_NEWMESSAGE, 0, 0) == 0)
  321.       {
  322.       report_error ("MSGF");
  323.       return 0;
  324.       }
  325.  
  326.    // ... and fill out the basic fields in the message.
  327.    
  328.    GetDlgItemText (hWnd, IDC_SUBJECT, buffer, sizeof (buffer));
  329.  
  330.    SendMessage (hParent, WM_F_SUBJECT, 0, (LPARAM) buffer);
  331.    if (IsDlgButtonChecked (hWnd, IDC_COPYSELF))
  332.       SendMessage (hParent, WM_F_COPYSELF, 1, 0);
  333.    if (IsDlgButtonChecked (hWnd, IDC_URGENT))
  334.       SendMessage (hParent, WM_F_URGENT, 1, 0);
  335.    if (IsDlgButtonChecked (hWnd, IDC_CONFIRMREADING))
  336.       SendMessage (hParent, WM_F_CONFIRMREADING, 1, 0);
  337.    if (IsDlgButtonChecked (hWnd, IDC_USEMIME))
  338.       SendMessage (hParent, WM_F_MIME, 1, 0);
  339.  
  340.    GetDlgItemText (hWnd, IDC_EMAIL, buffer, sizeof (buffer));
  341.    email_field = atoi (buffer);
  342.    if (email_field < 1) email_field = 1;
  343.    -- email_field;
  344.  
  345.    //  Now we're down to business: we parse the data file one
  346.    //  line at a time, creating a temporary file for each message
  347.    //  we create, then send it.
  348.  
  349.    while (fgets (buffer, sizeof (buffer), dfil) != NULL)
  350.       {
  351.       trim_newline (buffer);
  352.       if (buffer [0] == '\0') continue;      //  Blank line
  353.       i = 1;
  354.       s = buffer;
  355.       fields [0] = buffer;
  356.       while (*s)
  357.          {
  358.          if (strchr (seps, *s) != NULL)
  359.             {
  360.             *s = '\0';
  361.             if (i < MAXFIELDS)
  362.                fields [i ++] = s + 1;
  363.             }
  364.          ++ s;
  365.          }
  366.  
  367.       fields [i] = NULL;
  368.  
  369.       SendMessage (hParent, WM_F_TEMPFILE, sizeof (mfname), (LPARAM) mfname);
  370.       if ((mfil = fopen (mfname, "wt")) == NULL)
  371.          {
  372.          fclose (dfil);
  373.          fclose (ffil);
  374.          report_error ("TMPF");
  375.          return 0;
  376.          }
  377.  
  378.       dump_the_data (mfil, ffil, fields, i);
  379.       fclose (mfil);
  380.       SendMessage (hParent, WM_F_TO, 0, (LPARAM) (fields [email_field]));
  381.       SendMessage (hParent, WM_F_MESSAGEFILE, 0, (LPARAM) mfname);
  382.       SendMessage (hParent, WM_F_SENDMESSAGE, 0, 0);
  383.       }
  384.  
  385.    fclose (dfil);
  386.    fclose (ffil);
  387.    return 1;
  388.    }
  389.  
  390.  
  391. #pragma warn -use
  392.  
  393. LONG FAR PASCAL _export MrgProc (HWND hWnd, UINT wMsg, 
  394.    WPARAM wParam, LPARAM lParam)
  395.    {
  396.    //  Service routine for the form's enclosed dialog. This is a
  397.    //  standard windows modeless WndProc.
  398.  
  399.    DWORD dwResult = 0;
  400.    BOOL fCallDefProc = TRUE;
  401.    char buffer [256], *s, *to, *subj;
  402.    RECT r;
  403.  
  404.    switch (wMsg)
  405.       {
  406.       case WM_FM_INIT :
  407.          EnableWindow (GetDlgItem (hWnd, IDC_OTHEREDIT), FALSE);
  408.          CheckRadioButton (hWnd, IDC_TAB, IDC_OTHER, IDC_TAB);
  409.  
  410.          //  Now construct our help file name
  411.          GetModuleFileName (hLibInstance, buffer, sizeof (buffer));
  412.          s = strrchr (buffer, '.');
  413.          if (s != NULL)
  414.             {
  415.             strcpy (s, ".HLP");
  416.             strcpy (helpname, buffer);
  417.             }
  418.          break;
  419.  
  420.       case WM_DESTROY :
  421.          //  A good place to store some preferences, perhaps.
  422.          if (help_used) WinHelp (hWnd, helpname, HELP_QUIT, 0);
  423.          break;
  424.  
  425.       case WM_FM_INITFOCUS :
  426.          SetFocus (GetDlgItem (hWnd, 101));
  427.          break;
  428.  
  429.       case WM_FM_RESTOREFOCUS :
  430.          //  There's a glitch in the way the Windows MDI manager
  431.          //  seems to manage focus which means that we can have
  432.          //  focus taken away and not returned when the user does
  433.          //  anything outside our window. WinPMail has some quite
  434.          //  complex logic to deal with this case and will send
  435.          //  this message whenever focus should be restored in
  436.          //  our window. We set focus to the last active control
  437.          //  (which we know from trapping EN_SETFOCUS messages).
  438.  
  439.          if (last_focus) SetFocus (last_focus);
  440.          break;
  441.  
  442.       case WM_COMMAND :
  443.          fCallDefProc = FALSE;
  444.          if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_SETFOCUS)
  445.             {
  446.             //  We have to trap EN_SETFOCUS messages so we know which
  447.             //  control was active last. When a menu selection is made
  448.             //  our current control will lose focus and because of a
  449.             //  curiosity in Windows' MDI management, we won't get it
  450.             //  back. Instead, WinPMail generates a WM_FM_RESTOREFOCUS
  451.             //  message which signals to us that we should set focus to
  452.             //  whatever the last active control was.
  453.  
  454.             last_focus = (HWND) LOWORD (lParam);
  455.             break;
  456.             }
  457.  
  458.          switch (GET_WM_COMMAND_ID(wParam, lParam))
  459.             {
  460.             case IDC_TAB :
  461.                EnableWindow (GetDlgItem (hWnd, IDC_OTHEREDIT), FALSE);
  462.                CheckRadioButton (hWnd, IDC_TAB, IDC_OTHER, IDC_TAB);
  463.                break;
  464.  
  465.             case IDC_OTHER :
  466.                EnableWindow (GetDlgItem (hWnd, IDC_OTHEREDIT), TRUE);
  467.                CheckRadioButton (hWnd, IDC_TAB, IDC_OTHER, IDC_OTHER);
  468.                break;
  469.  
  470.             case IDC_BROWSEDATAFILE :
  471.                if (SendMessage (GetParent (hWnd), WM_F_BROWSEFILE, 1, (LPARAM) buffer))
  472.                   {
  473.                   SendDlgItemMessage (hWnd, IDC_DATAFILE, EM_SETSEL, 
  474.                      0, (LPARAM) 0x7FFF0000L);
  475.                   SendDlgItemMessage (hWnd, IDC_DATAFILE, EM_REPLACESEL,
  476.                      0, (LPARAM) buffer);
  477.                   }
  478.                break;
  479.  
  480.             case IDC_BROWSEFORMATFILE :
  481.                if (SendMessage (GetParent (hWnd), WM_F_BROWSEFILE, 1, (LPARAM) buffer))
  482.                   {
  483.                   SendDlgItemMessage (hWnd, IDC_FORMATFILE, EM_SETSEL, 
  484.                      0, (LPARAM) 0x7FFF0000L);
  485.                   SendDlgItemMessage (hWnd, IDC_FORMATFILE, EM_REPLACESEL,
  486.                      0, (LPARAM) buffer);
  487.                   }
  488.                break;
  489.  
  490.             case IDC_HELP :
  491.                if (helpname [0])
  492.                   {
  493.                   help_used = 1;
  494.                   WinHelp (hWnd, helpname, HELP_CONTEXT, 1);
  495.                   }
  496.                else
  497.                   MessageBeep (0);
  498.                break;
  499.  
  500.             case IDC_SEND :
  501.                if (! mailmerge (hWnd)) break;
  502.                //  Drop through and close up shop...
  503.  
  504.             case IDC_CANCEL :
  505.                PostMessage (GetParent (hWnd), WM_CLOSE, 0, 0);
  506.                break;
  507.             }
  508.          break;
  509.       }
  510.  
  511.    if (fCallDefProc)
  512.       dwResult = BWCCDefDlgProc (hWnd, wMsg, wParam, lParam);
  513.  
  514.    return dwResult;
  515.    }
  516.  
  517.  
  518. #pragma warn -sus
  519.  
  520. void unregister_form_classes (void)
  521.    {
  522.    //  Remove any classes associated with the form; we have the
  523.    //  same problem here as we do with registering the classes
  524.    //  for the DLL - we only want to deregister the classes on
  525.    //  the last time we're unloaded.
  526.  
  527.    if (GetModuleUsage (hLibInstance) > 1) return;      //  Not a problem
  528.    UnregisterClass (szFormDlgClassName, hLibInstance);
  529.    }
  530.  
  531.  
  532. BOOL FAR PASCAL LibMain (HINSTANCE hInst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine)
  533.    {
  534.    WNDCLASS wc;
  535.  
  536.    if (! hLibInstance)
  537.       {
  538.       hLibInstance = hInst;
  539.       BWCCGetVersion ();      //  Forces BWCC to be dynamically loaded.
  540.  
  541.       //  Register any window classes used by the form. Forms will usually
  542.       //  register either one or occasionally two classes which define
  543.       //  the composition and reader dialogs created by the DLL.
  544.       //
  545.       //  There's a gotcha here, of course (aren't there always, in
  546.       //  Windows?)... You can't register a window class more than once,
  547.       //  so if the DLL has already been loaded and the user asks to
  548.       //  create a second instance of the form, we have to be careful
  549.       //  not to re-register the class. We do this by checking the
  550.       //  instance usage counter of the DLL - if it's greater than 1,
  551.       //  then we DON'T register the classes.
  552.  
  553. #pragma warn -sig
  554.       wc.style          = WS_CHILD;
  555. #pragma warn .sig
  556.       wc.lpfnWndProc    = MrgProc;
  557.       wc.cbClsExtra     = 0;
  558.       wc.cbWndExtra     = DLGWINDOWEXTRA;
  559.        wc.hInstance      = hLibInstance;
  560.        wc.hIcon          = NULL;
  561.       wc.hCursor        = LoadCursor (NULL, IDC_ARROW);
  562.       wc.hbrBackground  = (HBRUSH) (COLOR_WINDOW + 1);
  563.       wc.lpszMenuName   = NULL;
  564.       wc.lpszClassName  = szFormDlgClassName;
  565.       if (! RegisterClass (&wc))
  566.          MessageBeep (0);
  567.       }
  568.  
  569.    return (TRUE);             // Initialization went OK
  570.    }
  571.  
  572.