Sample Application: EMailTest.dpr

Project source
Form source (Pascal)
Form source (DFM)

Design-time form image
Project source: EMailTest.dpr

program EMailTest;

{Test application for UnitOOPS OLE Drag and Drop Components}
uses
  Forms,
  fmEmailTest in 'fmEMailTest.pas' {Form1};

{$R *.RES}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
Back to top
Form source: fmEMailTest.pas

unit fmEMailTest;
{Test form UnitOOPS OLE Drag and Drop Components.  Demonstrates accepting drops
 of e-mail messages from Microsoft Outlook Express, Netscape (4.x, 6),
 Outlook (message and attachments), NeoPlanet or Eudora.

 Last modified: 11/28/2000}
interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  uoole, StdCtrls, ComCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    UOTextTarget1: TUOTextTarget;
    Panel1: TPanel;
    Label1: TLabel;
    Panel2: TPanel;
    Panel3: TPanel;
    ListView1: TListView;
    Panel4: TPanel;
    Memo1: TMemo;
    Panel5: TPanel;
    ckbSaveOutlookAsMsg: TCheckBox;
    SaveDialog1: TSaveDialog;
    procedure FormCreate(Sender: TObject);
    procedure UOTextTarget1Drop(Sender: TObject; Acceptor: TWinControl;
      const dropText: String; X, Y: Integer);
    procedure ListView1Change(Sender: TObject; Item: TListItem;
      Change: TItemChange);
    procedure FormDestroy(Sender: TObject);
    procedure UOTextTarget1DragEnter(Sender: TObject; effect: TDropEffect;
      X, Y: Integer);
  private
    { Private declarations }
    FSavedMsgNumber: integer;
    procedure UpdateMemo;
    function StripTagsFrom(const aString: string): string;
    procedure ProcessMailMessage(aSL: TStringList);

  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses
  ActiveX, ComObj, ShlObj, uoNetscape, uoUtil;

resourceString
  // clipboard format names for internet messages
  sOutlookExpressMessage = 'Athena Mail Messages';
  sOutlookExpressMessage5 = 'Outlook Express Messages';
  sNetscapeMessage = 'Netscape Message';
  sNetscape6Message = 'text/nsmessage';
  sEudoraMessage = 'EudoraTransferClipboardFormat';
  sFileGroupDescriptor = 'FileGroupDescriptor';
  sFileContents = 'FileContents';


{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
// Set up the TUOTextTarget to accept the appropriate custom formats
begin
  // Initialize
  FSavedMsgNumber := 1;

  with UOTextTarget1, CustomFormats do
  begin
    // Don't allow moves
    OverrideDropEffects[deMove] := deCopy;
    // Advertise that we accept these custom types
    // Outlook Express
    // AddObject(sOutlookExpressMessage, TObject(TYMED_HGLOBAL+TYMED_ISTREAM));
    Add(sOutlookExpressMessage);
    Add(sOutlookExpressMessage5);
    // Netscape
    Add(sNetscapeMessage);
    Add(sNetscape6Message);
    // Eudora
    Add(sEudoraMessage);
    // For Outlook, need both of the following.
    // See UOTextTarget1DragEnter for how to enforce that both formats are
    // present simultaneously.
    Add(sFileGroupDescriptor);
    AddObject(sFileContents, TObject(TYMED_ISTREAM));
    // For NeoPlanet, need CF_HDROP alone
    Add(IntToStr(CF_HDROP));
  end;    // with

end;

procedure TForm1.UpdateMemo;
// Update the memo to synchronize with the list view
var
  aLi: TListItem;
begin
  // Empty the memo
  Memo1.Clear;

  // First look for a selection
  aLi := ListView1.Selected;

  // If there's no selection, look for a focused item
  if not assigned(aLi) then
    aLi := ListView1.ItemFocused;

  // If we found one, put its message body in the memo
  if assigned(aLi) then
    Memo1.Lines.Text := PChar(aLi.Data);
end;

function TForm1.StripTagsFrom(const aString: string): string;
// Strip HTML tags from the string passed

  function StripEnclosedBy(const bString: string; const sTag: string): string;
  // Strip all material enclosed by (and including) <tag>...</tag>
  var
    openPos, closePos: integer;
    tag: string;
  begin
    result := bString;
    tag := UpperCase(sTag);
      // First, remove <TAG></TAG> tags  and everything between
    openPos := AnsiPos('<'+tag, UpperCase(result));
    while (openPos <> 0) do
    begin
      closePos := AnsiPos('</'+tag, UpperCase(result));
      if (closePos <> 0) then
        System.Delete(result, openPos, closePos-openPos+length(tag)+3) // Delete the </TAG>
      else
        System.Delete(result, openPos, length(result)-openPos+1); // Malformed (no </TAG>)
      openPos := AnsiPos('<'+tag+'>', UpperCase(result));
    end;    // while
  end;

var
  j, k: Integer;
  inTag, onlySpaces: boolean;
  s: string;
begin
  result := '';
  s := aString;

  // Kill everything enclosed in either <script></script> or <head></head>
  s := StripEnclosedBy(s, 'script');
  s := StripEnclosedBy(s, 'head');

  // Then, remove all other tags, LEAVING enclosed material
  inTag := false;
  for j := 1 to length(s) do    // Iterate
  begin
    // Figure out if we're inside a HTML tag
    if (s[j] = '<') then
      inTag := true;
    if inTag and (s[j] = '>') then
    begin
      inTag := false;
      // Tag is ended.  Skip to next iteration so we don't pick up '>'
      continue;
    end;

    // If not in a tag, we do a straight copy
    if not inTag then
      result := result + s[j];
  end;    // for

  // Finally, remove all multiple cr/lf combinations, i.e.
  // all lines that either a) are empty or b) contain only spaces
  with TStringList.Create do
  try
    Text := result; // Shove our string into the list
    for j := count downto 1 do    // Iterate
    begin
      // Delete any empty ones
      if (Strings[j-1] = '') then
        Delete(j-1)
      else
      begin
        // Look for lines that have only spaces
        onlySpaces := true;
        for k := 1 to length(Strings[j-1]) do    // Iterate
        begin
          onlySpaces := onlySpaces and (Strings[j-1][k] = ' ');
          // Don't finish the loop if we found a non-space!
          if not onlySpaces then break;
        end;    // for
        if onlySpaces then
          Delete(j-1);
      end;
    end;    // for
    // Put the list back onto the string.
    result := Text;
  finally
    Free; // the string list
  end;    // with

end;

procedure TForm1.ProcessMailMessage(aSL: TStringList);
// We have a modified RFC822-compliant e-mail message in the string list aSL
// in the form 'To=some address', (rather than 'To: some address') etc, etc .
// Parse it and put the resulting message into our user interface.
var
  aLi: TListItem;
  j: integer;
  spacerIndex: integer;
  aPos: integer;
  s: string;
  body: string;
begin
  // The headers end with 2 cr/lf's. That'll give a single blank string
  // in the string list at that index.  Find it.
  spacerIndex := aSL.IndexOf('');

  // Everything beyond that is the message body.
  body := aSL[spacerIndex+1];
  for j := spacerIndex+2 to aSL.Count-1 do    // Iterate
  begin
    body := body + #13#10 + aSL[j];
  end;    // for

  // Now erase the body and the spacer from the string list
  // Go backwards to avoid messing up the indices.
  for j := aSL.Count-1 downto spacerIndex do    // Iterate
  begin
    aSL.Delete(j);
  end;    // for

  // Everything that's left in the string list is header material.
  for j := 0 to spacerIndex-1 do    // Iterate
  begin
    // All headers are in the form "Header: value"
    // Change all ': ' to = so we can use the names/values
    // functionality of the string list
    aPos := pos(': ', aSL[j]);
    if (aPos <> 0) then
    begin
      s := aSL[j];
      // Delete the ':'
      System.Delete(s, aPos, 1);
      // Replace the ' ' with '='
      s[aPos] := '=';
      // And put it back in the list in the same position
      aSL[j] := s;
    end;
  end;    // for

  // If the body is HTML, do a simple-minded "remove tags" process
  // We could alternatively use a HTML viewer such as the Internet
  // Explorer control.
  // Only look at the first part of the 'COntent-Type' string (neglecting the
  // charset section)
  if AnsiCompareText(Copy(aSL.Values['Content-Type'], 1, 10), 'text/html;') = 0 then
    body := StripTagsFrom(body); // See function above

  // Add the new item to the list view
  aLi := ListView1.Items.Add;
  // Populate the list item
  with aLi do
  begin
    // The first column
    Caption := aSL.Values['From'];
    // The remaining columns
    with SubItems do
    begin
      // This one may be empty.  If so, first check if there's a 'Newsgroups'
      // entry.
      s := aSL.Values['To'];
      if (s = '') then
      begin
        s := aSL.Values['Newsgroups'];
        // If this isn't here either, then the e-mail is to the owner of the
        // mailbox
        if (s = '') then
          s := 'Me'
      end;
      Add(s);

      Add(aSL.Values['Subject']);

      Add(aSL.Values['Date']);

      // This one may be empty.  Put something in, if it is.
      s := aSL.Values['X-Mailer'];
      if (s = '') then
        s := '<unknown>';
      Add(s);

    end;    // with

    // Now, put the body of the message in the TListItem's Data property
    // We allocate a new PChar, which We'll free when deleting the TListItem,
    // in the form's OnDestroy handler.
    Data := StrNew(PChar(body));
  end;    // with

  // Make sure the memo is displayed
  ListView1.Selected := aLi;

end;


function FindEudoraFolder(eudora: variant; const folderName: string): variant;
// Find the first Eudora folder in the Eudora application object "eudora"
// with the name contained in "folderName".  There may be more than one folder
// with the same name, so this is only a 99% solution.
var
  theFolder: variant;

  procedure WalkFoldersIn(ParentFolder: variant);
  var
    j: integer;
    ChildFolder: variant;
    S: string;
  begin

    if (not varIsEmpty(theFolder)) then exit; // found already - bug out

    for j := 1 to ParentFolder.Folders.Count do    // Iterate
    begin
      ChildFolder := ParentFolder.Folders[j];
      s := ChildFolder.Name;

      if (AnsiCompareText(s, folderName) = 0) then
        theFolder := ChildFolder;

      if varIsEmpty(theFolder) and (ChildFolder.Folders.Count > 0) then
          WalkFoldersIn(ChildFolder);
    end;
  end;

begin
  // No folder yet found
  theFolder := unassigned;

  // Walk the folders recursively
  WalkFoldersIn(eudora.rootfolder);

  // Anything found is now in theFolder
  result := theFolder;
end;

procedure TForm1.UOTextTarget1Drop(Sender: TObject; Acceptor: TWinControl;
  const dropText: String; X, Y: Integer);
// Handle a drop of one or more e-mail messages.
// In its initial form, this method was a handler for Outlook Express messages
// only.  It subsequently grew to incorporate other e-mail clients.  It would
// probably benefit from being broken into several routines.  However, since then
// main task is to demonstrate getting content from several different e-mail
// clients, this code rearrangement is not a high priority.
const
  nSelectionsMax = 16383; // Win95 listbox limit on number of items
type
  TIntegerArray = array[0..nSelectionsMax-1] of integer;
  PIntegerArray = ^TIntegerArray;
var
  hasOutlookFormats: boolean;
  aSL: TStringList;
  s, t, sURL: string;
  aMS: TMemoryStream;
  aHWnd: HWnd;
  aIntArray: PIntegerArray;
  nSelections: integer;
  j: integer;
  eudora: variant;
  eudoraFolder: variant;
  eudoraMessage: variant;
  nFiles: integer;
  aFd: TFileDescriptor;
  anIStorage: IStorage;
  bSL: TStringList;
  OutlookHeaders: TStringList;
  formatCount: integer;
  messageFound: boolean;
  W: WideString;
  anIStream: IStream;
  aHRes: HRESULT;
  tempFileName: string;
  anIndex: integer;
  curAttachIndex: integer;
  attachmentFileName: string;
  curAttachStorageName: WideString;
  aSubStorage: IStorage;
  aString, tmpString: string;
begin
  messageFound := false;
  // Clear the memo
  Memo1.Clear;

  // Instantiate a string list for message processing
  aSL := TStringList.Create;
  try

     // *****Outlook Express*****

     // Is there an Outlook Express message here?
    if UOTextTarget1.DataObjectHasFormat(sOutlookExpressMessage) or
       UOTextTarget1.DataObjectHasFormat(sOutlookExpressMessage5) then
    begin
      messageFound := true;

      // Get the file group descriptor as there may be multiple messages.
      s := UOTextTarget1.DataObjectGetFormat(sFileGroupDescriptor);

      // How many fgd's in the returned data?  That's a DWORD at the head
      // of the string-encoded data
      nFiles := uoDecodeDWORDFromString(s);

      // Make a temporary file
      tempFileName := uoGetTempFileName('uox');
      // Process all of the files being dragged
      for j := 1 to nFiles do    // Iterate
      begin
        // Get the j'th file descriptor.  It starts after the initial DWORD,
        // and has the length of a TFileDescriptor.
        System.Move(s[SizeOf(DWORD)+1+(j-1)*SizeOf(TFileDescriptor)], aFd,
          SizeOf(TFileDescriptor));

        // Its content is on an IStream, at lindex j-1
        // Global overrides for lindex and tymed
        DataObjectLindex := j-1;
        DataObjectTymed := TYMED_ISTREAM;

        t := UOTextTarget1.DataObjectGetFormat(sFileContents);

        // Watch out for nothing at that index...
        if (t <> '') then
        begin
          // Get the IStream
          DWORD(anIStream) := uoDecodeDWORDFromString(t);

          // Put it on the temporary file
          uoSaveIStreamToFile(anIStream, tempFileName);

          // Reload into the string list
          // If you want to process any attachments they can be decoded here
          // since the message will be listed as multi-part MIME, etc.
          aSL.LoadFromFile(tempFileName);

          // Process the message to get it into the list view
          ProcessMailMessage(aSL);
        end; {if}
      end; {for j := 1...}

      aSL.Clear;
      
      // Delete the temporary file
      DeleteFile(tempFilename);

    end;


    // *****Netscape*****

    // Netscape only gives us a mailbox:/ url - we then have to go to the
    // Netscape OLE interface to get the actual message.
    // We look for Netcape's message clipboard format and text together.
    // Navigator 3.x supplies the former, but not the latter.
    if not messageFound and
       ((
         (UOTextTarget1.DataObjectHasFormat(sNetscapeMessage) and
          UOTextTarget1.DataObjectHasFormat(IntToStr(CF_TEXT))
       ) or
        UOTextTarget1.DataObjectHasFormat(sNetscape6Message)
       )) 
    then
    begin
      // Grab the (local) url for the message. This is supplied by Netscape in
      // several formats.  It's easiest to grab in text format.
      if UOTextTarget1.DataObjectHasFormat(IntToStr(CF_TEXT)) then
        sURL := UOTextTarget1.DataObjectGetFormat(IntToStr(CF_TEXT))
      else
      begin
        tmpString := UOTextTarget1.DataObjectGetFormat(sNetscape6Message);
        // The string that was returned is a wide string encoded byte-by-byte
        // on an AnsiString, so we pull only the odd ones out.
        // Accumulate the odd chars into a string
        sURL := '';
        for j := 1 to length(tmpString) do    // Iterate
        begin
          if odd(j) then
            sURL := sURL + tmpString[j];
        end;    // for

      end;

      messageFound := true;

      // Now, use the Netscape OLE interface to grab the message's text
      aMS := TMemoryStream.Create;
      SetCursor(Screen.Cursors[crHourglass]);
      try
        // Put the text on the newly-created memory stream.
        // Ignore the returned value, most likely 'message/rfc822'
        getNetscapeURLOnStream(sURL, aMS);
        // Then copy that stream to the string s
        SetLength(s, aMS.size);
        Move(aMS.Memory^, s[1], aMS.size);
        // Finally, assign the string to our string list.
        aSL.Text := s;
      finally
        aMS.Free;
        SetCursor(Screen.Cursors[crDefault]);
      end;

    end;

    // *****Eudora Pro 4.x*****

    // ****WARNING****HERE BE DRAGONS****WARNING****
    //
    // Tunnel in and get the listbox that Eudora uses for e-mail
    // listing display, then get the number of selections and their indices (since
    // Eudora's OLE interface doesn't support the concept of selected message).
    // THEN, use the OLE interface to grab the messages. (Phew)
    //
    // Look for both the Eudora clipboard format AND text.  Text is presented also
    // only when dragging from a Eudora listbox, which is precisely what we're looking
    // for.
    if (not messageFound) and UOTextTarget1.DataObjectHasFormat(sEudoraMessage) then
    begin
      if UOTextTarget1.DataObjectHasFormat(IntToStr(CF_TEXT)) then
      begin
        messageFound := true;
        aHWnd := FindWindow('EudoraMainWindow', nil); // Main window
        aHWnd := FindWindowEx(aHWnd, 0, 'MDIClient', nil); // MDI Client
        aHWnd := SendMessage(aHWnd, WM_MDIGETACTIVE, 0, 0); // Active MDI child

        // The title of the active MDI child window is the open mailbox window.  Get it.
        SetLength(s, 255);
        SetLength(s, GetWindowText(aHWnd, PChar(s), 255)); // Title of active mailbox

        // Now burrow further in until we get to the main listbox.
        aHWnd := FindWindowEx(aHWnd, 0, 'AfxMDIFrame42', nil);
        aHWnd := FindWindowEx(aHWnd, 0, '#32770', nil);
        aHWnd := FindWindowEx(aHWnd, 0, 'ListBox', nil); // Handle to message list

        // Now, get the selected item indices in the listbox.
        // Ridiculously large (128k) allocation in case the user tries to drag
        // 16383 messages.  It's not a big deal these days, I guess...
        GetMem(aIntArray, nSelectionsMax*sizeOf(integer));

        // Going with run-time automation, so put up an hourglass in case it takes
        // a while to locate the mailbox. By importing the Eudora type library and
        // doing vtable automation (interfaces) this can be made much faster, probably.
        SetCursor(Screen.Cursors[crHourglass]);
        try
          // Get the number of selections, and their (zero-based) indices, using the standard
          // LB_GETSELITEMS message for multi-select list boxes.
          nSelections := SendMessage(aHWnd, LB_GETSELITEMS, nSelectionsMax, integer(aIntArray));

          // We now know we have nSelections selections, with indices in aIntArray^, in the
          // mailbox whose name is "s".  We can now use OLE automation to grab the messages.
          try
            // At any point in the following, an EOleSysError could occur.
            try
              // Construct the Eudora application object
              eudora := CreateOleObject('Eudora.EuApplication.1');

              // Use a helper function (see above) to find the first mailbox that's named "s"
              eudoraFolder := FindEudoraFolder(eudora, s);
              // Oops - we could have done it using (untested, but according to the manual)
              // eudoraFolder := eudora.Folder(s, -1);

              // We only get the first mailbox named "s".  If it's the wrong one, then
              // the indices may be wrong.  Check first to avoid an exception.  Passing
              // here doesn't mean we've got the right one, by the way...
              if ((aIntArray^[nSelections-1]) < eudoraFolder.Messages.Count) then
              begin

                // Iterate over the selected messages
                for j := 1 to nSelections do
                begin
                  // listbox selections are zero-based, but Eudora uses 1-based message indexing.
                  eudoraMessage := eudoraFolder.Messages[(aIntArray^[j-1])+1];

                  // Take the raw RFC822 message, and give it to the string list.
                  // It will get processed in ProcessMailMessage() below
                  aSL.Text := eudoraMessage.RawMessage;
                  // The raw Eudora message doesn't, however, include the date
                  // Include it explicitly
                  aSL.Insert(0, 'Date: '+eudoraMessage.Date);

                  // And process it
                  ProcessMailMessage(aSL);

                  // And clear out the string list for the next time around, or below.
                  aSL.Clear;
                end;    // for

              end
              else
              begin
                 MessageBox(Self.Handle, PChar('The drop cannot proceed.' + #13 + #10 + #13 + #10 + 'You ' +
                'probably have more than one folder named "'+s+'".  This demo found only the first ' +
                'one, which is obviously not the correct one, as it doesn''t have enough messages in it.'),
                PChar('UnitOOPS EMailTest Demo'), MB_OK + MB_ICONERROR + MB_SETFOREGROUND);
              end;
            finally
              // Throw away the automation object

              eudoraFolder := unAssigned;
              eudora := unAssigned;
            end;

          except
            on EOleSysError do
            begin
              MessageBox(Self.Handle, PChar('The Eudora automation server could not be started.'
                + #13 + #10 + #13 + #10 + 'Please ' +
                'ensure that you have enabled automation in Eudora (Tools | Options | Automation ' +
                'and check "Automation enabled from this machine".  Alternatively, stop Eudora, ' +
                'and start it once with the command line "Eudora /EnableAutomation"'),
                'UnitOOPS EMailTest Demo', MB_OK + MB_ICONERROR + MB_SETFOREGROUND);
            end;
          end;

        finally
          FreeMem(aIntArray, nSelectionsMax*sizeOf(integer));
          SetCursor(Screen.Cursors[crDefault]);
        end;
      end
      else
      begin
        // Eudora format, but no CF_TEXT.
        MessageBox(Self.Handle, 'E-mail messages can only be dragged here from a Eudora list view.',
          'UnitOOPS FileTest Demo', MB_OK + MB_ICONWARNING + MB_SETFOREGROUND);
      end;
    end;

    // *****Outlook*****

    // Do we have fgd?
    hasOutlookFormats := (not messageFound)
      and UOTextTarget1.DataObjectHasFormat(sFileGroupDescriptor);

    // Global override for tymed - reset automatically by calls to
    // DataObjectHasFormat and DataObjectGetFormat
    DataObjectTymed := TYMED_ISTREAM;

    // Do we have fc?  Do the call first, so we're guaranteed that it won't
    // be short-circuited, so that the global tymed and lindex get reset properly.
    hasOutlookFormats := UOTextTarget1.DataObjectHasFormat(sFileContents) and hasOutlookFormats;

    // We have the formats we're looking for.
    if hasOutlookFormats then
    begin
      messageFound := true;
      // Get the file group descriptors
      s := UOTextTarget1.DataObjectGetFormat(sFileGroupDescriptor);
      // How many fgd's in the returned data?  That's a DWORD at the head
      // of the string-encoded data
      nFiles := uoDecodeDWORDFromString(s);

      // Get the headers, too
      bSL := TStringList.Create;
      OutlookHeaders := nil;
      try
        if UOTextTarget1.DataObjectHasFormat(IntToStr(CF_TEXT)) then
        begin
          bSL.Text := UOTextTarget1.DataObjectGetFormat(IntToStr(CF_TEXT));
        end;

        // Figure out the current headers displayed in Outlook.  Outlook only
        // supplies, in a drop, the headers that are actually displayed.
        // Also, the user may have dragged them into unusual orders.
        // Here we'll get the header names and orders as currently displayed, and
        // use them below to decide which header is in which position.
        OutLookHeaders := uoTokenizeRaw(bSL[0], [#9]);
        for j := 1 to OutLookHeaders.Count do    // Iterate
        begin
          // Append = so we can use names/values string list functions
          OutlookHeaders[j-1] := OutlookHeaders[j-1]+'=';
        end;    // for

        // Make a temporary file
        tempFileName := uoGetTempFileName('uox');
        // Process all of the files being dragged
        for j := 1 to nFiles do    // Iterate
        begin
          // Get the j'th file descriptor.  It starts after the initial DWORD,
          // and has the length of a TFileDescriptor.
          System.Move(s[SizeOf(DWORD)+1+(j-1)*SizeOf(TFileDescriptor)], aFd,
            SizeOf(TFileDescriptor));

          // Its content is on an IStream, at lindex j-1
          // Global overrides for lindex and tymed
          DataObjectLindex := j-1;
          DataObjectTymed := TYMED_ISTORAGE;
          t := UOTextTarget1.DataObjectGetFormat(sFileContents);

          // Watch out for nothing at that index...
          if (t <> '') then
          begin
            // Get the IStorage
            DWORD(anIStorage) := uoDecodeDWORDFromString(t);

            // Are we saving Outlook as MSG files? (A saved .MSG file can be double-
            // clicked to start up Outlook editing that message).
            // This is just an exercise in putting the IStorage on a file.
            // How?  Make another storage that IS a file, and copy to there!
            // Because the default is direct (as opposed to transacted) mode,
            // the change gets saved immediately.
            if ckbSaveOutlookAsMsg.Checked then
            begin
              // Make sure the user sees the dialog.
              SetForegroundWindow(Handle);

              with SaveDialog1 do
              begin
                // Preset filename in save dialog
                FileName := Format('Dropped Outlook Message #%d.msg', [FSavedMsgNumber]);
                // Execute the save dialog
                if Execute then
                begin
                  // uoSaveIStorageToFile() introduced in V1.31
                  uoSaveIStorageToFile(anIStorage, SaveDialog1.FileName);
                  inc(FSavedMsgNumber);
                end;
              end;    // with
            end;


            // ****
            W := '__substg1.0_1000001E'; // Seems to be the stream name for message text
            aHRes := anIStorage.OpenStream(PWideChar(w), nil,
              STGM_READ or STGM_SHARE_EXCLUSIVE, 0, anIStream);
            if SUCCEEDED(aHRes) then
            begin

              // Put the IStream contents on the file
              uoSaveIStreamToFile(anIstream, tempFileName);
              // Load it into a string list
              aSL.LoadFromFile(tempFilename);
              // Add the headers back in RFC822 format.  Check for existence in the
              // current Outlook view, and whether they've been rearranged.
              if (bSL.Count > j) then
              begin
                with uoTokenizeRaw(bSL[j], [#9]) do
                try
                  aSL.Insert(0, '');
                  anIndex := OutlookHeaders.IndexOfName('To');
                  if (anIndex >= 0) then aSL.Insert(0, 'To: '+strings[anIndex]);
                  anIndex := OutlookHeaders.IndexOfName('Subject');
                  if (anIndex >= 0) then aSL.Insert(0, 'Subject: '+strings[anIndex]);
                  anIndex := OutlookHeaders.IndexOfName('Received');
                  if (anIndex >= 0) then aSL.Insert(0, 'Date: '+strings[anIndex]);
                  anIndex := OutlookHeaders.IndexOfName('From');
                  if (anIndex >= 0) then aSL.Insert(0, 'From: '+strings[anIndex]);
                finally
                  Free;
                end;    // with
              end;

              // Now, look for attachments.
              // Initialize counter and string
              curAttachIndex := 0;
              aString := '';
              repeat
                // Form the storage name for attachment with index curAttachIndex
                curAttachStorageName := Format('__attach_version1.0_#%8.8d', [curAttachIndex]);

                // Now attempt to open that storage
                aHRes := anIStorage.OpenStorage(PWideChar(curAttachStorageName), nil,
                  STGM_READ or STGM_SHARE_EXCLUSIVE, nil, 0, aSubStorage);
                // If we succeed, we have a storage of that name, i.e. an attachment
                if SUCCEEDED(aHRes) then
                begin
                  // Find the file name for the attachment
                  W := '__substg1.0_3001001E'; // Name
                  // Open the stream
                  aSubStorage.OpenStream(PWideChar(w), nil,
                    STGM_READ or STGM_SHARE_EXCLUSIVE, 0, anIStream);
                  uoSaveIStreamToFile(anIStream, tempFileName);
                  with TStringList.Create do
                  try
                    LoadFromFile(tempFileName);
                    attachmentFileName := strings[0];
                    // Prepend the temp path to the file name to get a fully-
                    // qualified file path for saving the attachment
                    attachmentFileName := uoGetTempPath+attachmentFileName;
                    // Add attachment to list for reporting via messagebox
                    aString := aString + attachmentFileName + #13#10;
                  finally // wrap up
                    Free;
                  end;    // try/finally

                  // Read the contents of the attachment
                  W := '__substg1.0_37010102'; // Contents
                  // Open the stream
                  aSubStorage.OpenStream(PWideChar(w), nil,
                    STGM_READ or STGM_SHARE_EXCLUSIVE, 0, anIStream);
                  // Save it to the file name obtained above
                  uoSaveIStreamToFile(anIStream, attachmentFileName);
                end;

                inc(curAttachIndex);

              until FAILED(aHRes);

              // Pop up a message if there were attachments (now saved) in the
              // Outlook message currently being dropped (this messagebox could
              // fire once for every message dropped).
              if (curAttachIndex > 1) then
              begin
                aString := '%d attachment(s) were found in this Outlook message.  They have '
                  + 'been copied to the following locations:'#13#10#13#10
                  + aString;
                aString := Format(aString, [curAttachIndex-1]);
                Application.MessageBox(PChar(aString), 'UnitOOPS EMailTest Demo', MB_OK + MB_ICONINFORMATION + MB_SETFOREGROUND);
              end;

              // Delete the temporary file
              DeleteFile(tempFilename);

              // And process the message
              ProcessMailMessage(aSL);
            end;

          end;

          // Clear the string list for the next go-around
          aSL.Clear;
        end;
      finally
        // Clear up temporary string lists
        bSL.Free;
        OutlookHeaders.Free;
      end;
    end;

    // *****NeoPlanet*****
    if (not messageFound)
      and UOTextTarget1.DataObjectHasFormat(IntToStr(CF_HDROP)) then
    begin
      bSL := UOTextTarget1.DataObjectFormatList;
      try
        formatCount := bSL.Count;
      finally
        bSL.Free;
      end;

      if (formatCount = 1) then
      begin
        // Get the HDROP format
        s := UOTextTarget1.DataObjectGetFormat(IntToStr(CF_HDROP));

        // Get the list of files (one per e-mail)
        bSL := uoFileListFromHDROP(s);
        try
          // Now iterate over these files, and pull in the messages.  Each of these
          // files contains a RFC822 message.
          for j := 1 to bSL.Count do    // Iterate
          begin
            aSL.LoadFromFile(bSL[j-1]);
            ProcessMailMessage(aSL);
            aSL.Clear;
          end;    // for
        finally
          bSL.Free;
        end;
      end;

    end;


    // Now, if aSL has any content, it has a message.  Both Netscape and OE use
    // RFC 822 format, so we process them in the same manner.  This has already
    // been called above for Outlook and Eudora, which can transfer multiple
    // messages at once.
    if (aSL.Count > 0) then
      ProcessMailMessage(aSL);

  finally
    aSL.Free;

    // Make sure the bottom-most item in the list view is fully visible.
    ListView1.Items[ListView1.Items.Count-1].MakeVisible(false);
  end;
end;

procedure TForm1.ListView1Change(Sender: TObject; Item: TListItem;
  Change: TItemChange);
// The list view has changed. Make sure the memo is synchronized properly.
begin
  UpdateMemo;
end;

procedure TForm1.FormDestroy(Sender: TObject);
var
  j: Integer;
begin
  // Clean up - delete the data element of each list item where we
  // stored a message body
  with ListView1 do
  begin
    for j := 1 to Items.Count do    // Iterate
    begin
      if assigned(Items[j-1].Data) then
        StrDispose(PChar(Items[j-1].Data));
    end;    // for
  end;    // with
end;

procedure TForm1.UOTextTarget1DragEnter(Sender: TObject;
  effect: TDropEffect; X, Y: Integer);
// Ensure that, if we have FileContents, we also have FileGroupDescriptor
// (Both are needed for Outlook).  This is the standard way of enforcing that
// multiple formats occur together.  We only need this in OnDragEnter.
// Ensure that we don't accidentally block Netscape messages.
begin
  // If we have FileGroupDescriptor, we MUST have FileContents (Outlook)
  with (Sender as TUOTextTarget) do
  begin
    // Reset all of the drop effects to "Copy" (i.e., disallow move, link)
    OverrideDropEffects[deMove] := deCopy;
    OverrideDropEffects[deCopy] := deCopy;
    OverrideDropEffects[deLink] := deCopy;
    // Do we have fgd?
    if DataObjectHasFormat(sFileGroupDescriptor) and not
       DataObjectHasFormat(sNetscapeMessage) then
    begin

      // Global override for tymed - reset automatically by calls to
      // DataObjectHasFormat and DataObjectGetFormat
      DataObjectTymed := TYMED_ISTREAM;

      // Do we have fc?  Do the call first, so we're guaranteed that it won't
      // be short-circuited, so that the global tymed and lindex get reset properly.
      if not DataObjectHasFormat(sFileContents) then
      begin
        // We don't have both formats.  All drop effects will be interpreted as
        // "nothing", i.e., ignore the drop.  Since we're in OnDragEnter, this
        // means that a no-drop cursor is all the user will see.
        OverrideDropEffects[deMove] := deNone;
        OverrideDropEffects[deCopy] := deNone;
        OverrideDropEffects[deLink] := deNone;
      end;
    end;
  end;
end;

end.
Back to top
Form source: fmEMailTest.dfm

object Form1: TForm1
  Left = 210
  Top = 148
  Width = 625
  Height = 309
  Caption = 'Test of dropping News and E-mail items'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  OnDestroy = FormDestroy
  PixelsPerInch = 96
  TextHeight = 13
  object Panel1: TPanel
    Left = 0
    Top = 0
    Width = 617
    Height = 33
    Align = alTop
    BevelOuter = bvNone
    BorderWidth = 3
    Caption = 'Panel1'
    TabOrder = 0
    object Label1: TLabel
      Left = 3
      Top = 3
      Width = 611
      Height = 27
      Align = alClient
      Caption = 
        'Drag an e-mail or news message from Outlook Express, Netscape, C' +
        'ollabra, Outlook or Eudora 4.x onto the window below.'
      WordWrap = True
    end
  end
  object Panel2: TPanel
    Left = 0
    Top = 33
    Width = 617
    Height = 249
    Align = alClient
    BevelOuter = bvNone
    BorderWidth = 3
    TabOrder = 1
    object Panel3: TPanel
      Left = 3
      Top = 3
      Width = 611
      Height = 118
      Align = alTop
      BevelOuter = bvNone
      BorderWidth = 3
      Caption = 'Panel3'
      TabOrder = 0
      object ListView1: TListView
        Left = 3
        Top = 3
        Width = 605
        Height = 112
        Align = alClient
        ColumnClick = False
        Columns = <
          item
            Caption = 'From'
            Width = 100
          end
          item
            Caption = 'To'
            Width = 100
          end
          item
            Caption = 'Subject'
            Width = 150
          end
          item
            Caption = 'Date'
            Width = 150
          end
          item
            Caption = 'Mailer'
            Width = 100
          end>
        ReadOnly = True
        RowSelect = True
        OnChange = ListView1Change
        TabOrder = 0
        ViewStyle = vsReport
      end
    end
    object Panel4: TPanel
      Left = 3
      Top = 121
      Width = 611
      Height = 125
      Align = alClient
      BevelOuter = bvNone
      BorderWidth = 3
      Caption = 'Panel4'
      TabOrder = 1
      object Memo1: TMemo
        Left = 3
        Top = 3
        Width = 605
        Height = 119
        Align = alClient
        ScrollBars = ssVertical
        TabOrder = 0
      end
    end
  end
  object UOTextTarget1: TUOTextTarget
    AcceptorControl = Panel2
    AcceptTextFormats = [dtfCustom]
    OnDragEnter = UOTextTarget1DragEnter
    OnDrop = UOTextTarget1Drop
    Left = 496
    Top = 88
  end
end
Back to top

Back to the examples page