home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / imap-3.0 / MailManager / SendWindow.m < prev    next >
Encoding:
Text File  |  1993-04-15  |  21.1 KB  |  780 lines

  1. /*
  2.  * Program:    Distributed Electronic Mail Manager (SendWindow object)
  3.  *
  4.  * Author:    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    24 February 1989
  13.  * Last Edited:    15 April 1993
  14.  *
  15.  * Copyright 1993 by the University of Washington
  16.  *
  17.  *  Permission to use, copy, modify, and distribute this software and its
  18.  * documentation for any purpose and without fee is hereby granted, provided
  19.  * that the above copyright notice appears in all copies and that both the
  20.  * above copyright notice and this permission notice appear in supporting
  21.  * documentation, and that the name of the University of Washington not be
  22.  * used in advertising or publicity pertaining to distribution of the software
  23.  * without specific, written prior permission.  This software is made
  24.  * available "as is", and
  25.  * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
  26.  * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
  27.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
  28.  * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
  29.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  30.  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
  31.  * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
  32.  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  33.  *
  34.  */
  35.  
  36.  
  37. #import "MailManager.h"
  38. #import "XTextPkg.h"        // mdd
  39.  
  40. @implementation SendWindow
  41.  
  42. // Create a new SendWindow
  43.  
  44. + new
  45. {
  46.   char tmp[TMPLEN];
  47.   char *user = *username ? username :
  48.     ((lastlogin && *lastlogin) ? lastlogin : localuser);
  49.   self = [super new];        // create ourselves
  50.                 // get the interface
  51.   [NXApp loadNibSection:"SendWindow.nib" owner:self withNames:NIL];
  52.                 // get handle on address book
  53.   book = [NXApp addressBook:NIL];
  54.   sound = NIL;            // no sound yet
  55.   select = T;            // OK to call selectBody in actions
  56.   env = mail_newenvelope ();    // instantiate the envelope
  57.   attachments = NIL;        // no attachments yet
  58.   env->remail = NIL;        // not a remail (yet)
  59.   rfc822_date (tmp);        // get the date now
  60.   env->date = cpystr (tmp);    // set up date string
  61.   env->subject = NIL;        // initially no subject
  62.                 // calculate Return-Path:
  63.   sprintf (tmp,"%s@%s",user,domainname);
  64.   rfc822_parse_adrlist (&env->return_path,tmp,(char *) domainname);
  65.                 // calculate Sender:
  66.   if (*localpersonal) sprintf (tmp,"%s <%s@%s>",
  67.                    localpersonal,localuser,localhost);
  68.   else sprintf (tmp,"%s@%s",localuser,localhost);
  69.   if (strcmp (user,localuser) || strcmp (domainname,localhost))
  70.     rfc822_parse_adrlist (&env->sender,tmp,localhost);
  71.                 // calculate From: field
  72.   if (*personalname)
  73.     sprintf (tmp,"%s <%s@%s>",personalname,user,domainname);
  74.   else sprintf (tmp,"%s@%s",user,domainname);
  75.   rfc822_parse_adrlist (&env->from,tmp,(char *) domainname);
  76.   env->reply_to = env->to = env->cc = env->bcc = NIL;
  77.                 // generate message ID
  78.   sprintf (tmp,"<%s.%ld.%d.%s@%s>",[NXApp appName],time (0),getpid (),
  79.        localuser,localhost);
  80.   env->message_id = cpystr (tmp);
  81.   env->in_reply_to = NIL;
  82.                 // set TAB actions
  83.   if (from) [from setNextText:[replyTo setNextText:[to setNextText:
  84.          [cc setNextText:[bcc setNextText:[subject setNextText:from]]]]]];
  85.   else [to setNextText:[cc setNextText:[subject setNextText:to]]];
  86.   [self updateEnvelopeView];    // set the envelope views from msg
  87.   if (defaultcc && *defaultcc)
  88.     [self setAddress:[cc setStringValue:defaultcc] :&env->cc];
  89.   if (defaultbcc && *defaultbcc)
  90.     [self setAddress:[bcc setStringValue:defaultbcc] :&env->bcc];
  91.   [window makeKeyWindow];    // make us the key window
  92.   return self;            // give us back to caller
  93. }
  94.  
  95. // Note our window
  96.  
  97. - setWindow:anObject
  98. {
  99.   window = anObject;        // note our window
  100.   [window setDelegate:self];    // we're the window's delegate
  101.   return self;
  102. }
  103.  
  104.  
  105. // Note our From
  106.  
  107. - setFrom:anObject
  108. {
  109.   [[(from = anObject) setFont:defaultfont] setTextDelegate:self];
  110.   return self;
  111. }
  112.  
  113.  
  114. // Note our ReplyTo
  115.  
  116. - setReplyTo:anObject
  117. {
  118.   [[(replyTo = anObject) setFont:defaultfont] setTextDelegate:self];
  119.   return self;
  120. }
  121.  
  122.  
  123. // Note our To
  124.  
  125. - setTo:anObject
  126. {
  127.   [[(to = anObject) setFont:defaultfont] setTextDelegate:self];
  128.   return self;
  129. }
  130.  
  131.  
  132. // Note our Cc
  133.  
  134. - setCc:anObject
  135. {
  136.   [[(cc = anObject) setFont:defaultfont] setTextDelegate:self];
  137.   return self;
  138. }
  139.  
  140. // Note our bcc
  141.  
  142. - setBcc:anObject
  143. {
  144.   [[(bcc = anObject) setFont:defaultfont] setTextDelegate:self];
  145.   return self;
  146. }
  147.  
  148.  
  149. // Note our Subject
  150.  
  151. - setSubject:anObject
  152. {
  153.   [[(subject = anObject) setFont:defaultfont] setTextDelegate:self];
  154.   return self;
  155. }
  156.  
  157.  
  158. // Note our envelope view
  159.  
  160. - setEnvelopeView:anObject
  161. {
  162.   [(envelopeView = anObject) getFrame:&envelopeFrame];
  163.   return self;
  164. }
  165.  
  166.  
  167. // Note our bodyView
  168.  
  169. - setBodyView:anObject
  170. {
  171.   NXRect frame;
  172.                 // note our view and get its frame
  173.   [(bodyView = [anObject docView]) getFrame:&frame];
  174.   [bodyView setInitialAction: xtext_action];        // mdd
  175.                 // reset its font
  176.   [bodyView renewFont:defaultfont text:"" frame:&frame tag:0];
  177.   [bodyView setDelegate:self];    // we're our own delegate
  178.   [bodyView setSelectable:T];    // we can select text
  179.   [bodyView setEditable:T];    // the body view is editable
  180.   [bodyView setMonoFont:T]; // mdd (can't set this in IB anymore)
  181.                 // set up initial selection
  182.   [bodyView setSel:(start = 0) :(end = 0)];
  183.   return self;
  184. }
  185.  
  186. // Note our attachment panel
  187.  
  188. - setAttachmentPanel:anObject
  189. {
  190.   attachmentPanel = anObject;    // note our attachment panel
  191.   return self;
  192. }
  193.  
  194.  
  195. // Note our attachment view
  196.  
  197. - setAttachmentView:anObject
  198. {
  199.   NXSize size;
  200.   NXRect frame;
  201.   [anObject getFrame:&frame];    // get frame of attachments view
  202.                 // create a scroll view
  203.   [(attachmentView = [ScrollView newFrame:&frame]) setBorderType:NX_BEZEL];
  204.                 // only vertical scrolling is required
  205.   [attachmentView setVertScrollerRequired:T];
  206.                 // allow its size to change
  207.   [attachmentView setAutosizing:NX_HEIGHTSIZABLE|NX_WIDTHSIZABLE];
  208.                 // put up the view
  209.   [[anObject superview] replaceSubview:anObject with:attachmentView];
  210.                 // get the size minus the scroll bars
  211.   [attachmentView getContentSize:&size];
  212.                 // set it as such in the browser frames
  213.   NX_WIDTH (&frame) = size.width = 1300.0; NX_HEIGHT (&frame) = size.height;
  214.                 // create the browser
  215.   browser = [Matrix newFrame:&frame mode:NX_LISTMODE
  216.          cellClass:[ActionCell class] numRows:0 numCols:0];
  217.   size.height = 13.0;        // large enough to hold the text
  218.   [browser setCellSize:&size];    // set size of each cell
  219.   size.width = size.height = 0;    // no intercell spacing
  220.   [browser setIntercell:&size];
  221.   [browser setAutoscroll:T];    // make it autoscrolling
  222.                 // put the browser in the scroller and display
  223.   [[attachmentView setDocView:browser] display];
  224.   return self;
  225. }
  226.  
  227. // Non-interface methods
  228.  
  229.  
  230. // Return bodyView
  231.  
  232. - bodyView
  233. {
  234.   return bodyView;        // return the bodyView
  235. }
  236.  
  237.  
  238. // Return envelope for window
  239.  
  240. - (ENVELOPE *) msg
  241. {
  242.   return env;            // return the envelope
  243. }
  244.  
  245.  
  246. // Return window
  247.  
  248. - window
  249. {
  250.   return window;        // return the window
  251. }
  252.  
  253. // Open the send help panel
  254.  
  255. - help:sender
  256. {
  257.   [NXApp sendHelp:sender];
  258.   return self;
  259. }
  260.  
  261.  
  262. // From: field changed
  263.  
  264. - from:sender
  265. {
  266.   return [self setAddress:from :&env->from];
  267. }
  268.  
  269.  
  270. // Reply-To: field changed
  271.  
  272. - replyTo:sender
  273. {
  274.   return [self setAddress:replyTo :&env->reply_to];
  275. }
  276.  
  277.  
  278. // To: field changed
  279.  
  280. - to:sender
  281. {
  282.   return [self setAddress:to :&env->to];
  283. }
  284.  
  285.  
  286. // cc: field changed
  287.  
  288. - cc:sender
  289. {
  290.   return [self setAddress:cc :&env->cc];
  291. }
  292.  
  293.  
  294. // bcc: field changed
  295.  
  296. - bcc:sender
  297. {
  298.   return [self setAddress:bcc :&env->bcc];
  299. }
  300.  
  301. // Set an address list
  302.  
  303. - setAddress:view :(ADDRESS **) list
  304. {
  305.   ADDRESS *adr;
  306.   char tmp[16*TMPLEN];
  307.   int i = strlen (domainname);
  308.   if (view) {
  309.                 // flush old list
  310.     if (*list) mail_free_address (list);
  311.     memset (tmp,'@',i);        // flag for non-specified host
  312.     tmp[i] = '\0';        // tie off string
  313.                 // get new address list
  314.     rfc822_parse_adrlist (list,(char *) [view stringValue],tmp);
  315.     if (adr = *list) {        // if first address needs looking up
  316.       if (adr->host && adr->host[0] == '@') [book lookup:list];
  317.       adr = *list;        // point at list here in case lookup: munged it
  318.       while (adr->next) {    // lookup next address
  319.     if (adr->next->host && adr->next->host[0] == '@')
  320.       [book lookup:&adr->next];
  321.     adr = adr->next;    // try address after this one
  322.       }
  323.     }
  324.     tmp[0] = '\0';        // start with empty string
  325.                 // reverse parse the list
  326.     rfc822_write_address (tmp,*list);
  327.                 // set the new value
  328.     [[view setStringValue:tmp] display];
  329.   }
  330.   if (select) [self selectBody];// select the body view
  331.   return self;
  332. }
  333.  
  334.  
  335. // Subject: field changed
  336.  
  337. - subject:sender
  338. {
  339.   char *sub = (char *) [subject stringValue];
  340.                 // free any old subject
  341.   if (env->subject) fs_give ((void **) &env->subject);
  342.                 // copy subject as new value
  343.   if (strlen (sub)) env->subject = cpystr (sub);
  344.   if (select) [self selectBody];// select the body view
  345.   return self;
  346. }
  347.  
  348.  
  349. // Envelope item has lost first responder status
  350.  
  351. - textDidEnd:textObject endChar:(unsigned short) whyEnd
  352. {
  353.   id field = [textObject superview];
  354.                 // do action if tabbing
  355.   if (textObject != bodyView && (whyEnd == NX_TAB || whyEnd == NX_BACKTAB)) {
  356.     select = NIL;        // don't call selectBody, do the action
  357.     [field sendAction:[field action] to:self];
  358.     select = T;            // OK to call selectBody in actions again
  359.   }
  360.   return self;
  361. }
  362.  
  363. // Body view is losing first responder status
  364.  
  365. - (BOOL) textWillEnd:textObject
  366. {
  367.   NXSelPt s,e;
  368.   if (textObject == bodyView) {    // remember position if the body view
  369.     [bodyView getSel:&s :&e];    // get current selection
  370.     start = s.cp; end = e.cp;    // remember selection
  371.   }
  372.   return NIL;            // allow it
  373. }
  374.  
  375.  
  376. // Select the body view
  377.  
  378. - selectBody
  379. {
  380.                 // select to if none there yet
  381.   if (!env->to) return [to selectText:self];
  382.                 // select subject if none there yet
  383.   if (!env->subject) return [subject selectText:self];
  384.                 // become first responder again
  385.   [[bodyView setSel:start :end] display];
  386.   return self;
  387. }
  388.  
  389.  
  390. // Update envelope views from msg
  391.  
  392. - updateEnvelopeView
  393. {
  394.   char tmp[16*TMPLEN];
  395.   tmp[0] = '\0';        // update From
  396.   rfc822_write_address (tmp,env->from);
  397.   [[from setStringValue:tmp] display];
  398.   tmp[0] = '\0';        // update Reply-To
  399.   rfc822_write_address (tmp,env->reply_to);
  400.   [[replyTo setStringValue:tmp] display];
  401.   tmp[0] = '\0';        // update To
  402.   rfc822_write_address (tmp,env->to);
  403.   [[to setStringValue:tmp] display];
  404.   tmp[0] = '\0';        // update cc
  405.   rfc822_write_address (tmp,env->cc);
  406.   [[cc setStringValue:tmp] display];
  407.   tmp[0] = '\0';        // update bcc
  408.   rfc822_write_address (tmp,env->bcc);
  409.   [[bcc setStringValue:tmp] display];
  410.                 // update Subject
  411.   [[subject setStringValue:env->subject] display];
  412.   [self selectBody];        // put cursor back in the body view
  413.   return self;
  414. }
  415.  
  416. // Get text from a file
  417.  
  418. - insertFile:sender
  419. {
  420.   NXStream *file;
  421.   char *text;
  422.   int i,j;
  423.   OpenPanel *query = [OpenPanel new];
  424.   if ([query runModal]) {    // if got a file
  425.                 // try to open it
  426.     if (!(file = NXMapFile ([query filename],NX_READONLY)))
  427.       NXRunAlertPanel ("Open Failed","Can't open file",NIL,NIL,NIL);
  428.     else {            // get pointer to the text
  429.       NXGetMemoryBuffer (file,&text,&i,&j);
  430.                 // insert the text
  431.       [[bodyView replaceSel:text] display];
  432.                 // flush the file
  433.       NXCloseMemory (file,NX_FREEBUFFER);
  434.     }
  435.   }
  436.   return self;
  437. }
  438.  
  439.  
  440. // Attach a file
  441.  
  442. - attachFile:sender
  443. {
  444.   char *s,*t;
  445.   int i,j;
  446.   PART *part;
  447.   NXStream *file;
  448.   OpenPanel *query = [OpenPanel new];
  449.                 // get file name from user & open file
  450.   if ([query runModal] && (s = (char *) [query filename])) {
  451.     if (!(file = NXMapFile (s,NX_READONLY)))
  452.       NXRunAlertPanel ("Attach failed","Can't open attachment",NIL,NIL,NIL);
  453.     else {            // locate file data
  454.       NXGetMemoryBuffer (file,&t,&i,&j);
  455.                 // make an attachment
  456.       part = [self attachFile:s data:t size:i];
  457.                 // close the file
  458.       NXCloseMemory (file,NX_FREEBUFFER);
  459.       if (part) {        // set as application octet data
  460.     part->body.type = TYPEAPPLICATION;
  461.     part->body.subtype = cpystr ("OCTET-STREAM");
  462.     t = strrchr (s,'/');    // find basic file name
  463.     part->body.parameter = mail_newbody_parameter ();
  464.     part->body.parameter->attribute = cpystr ("NAME");
  465.     part->body.parameter->value = cpystr (t ? ++t : s);
  466.     part->body.description = cpystr ("attached file");
  467.       }
  468.     }
  469.   }
  470.   return self;
  471. }
  472.  
  473. // Worker routine for attachFile: and attachSound:
  474.  
  475. - (PART *) attachFile:(char *) name data:(char *) data size:(int) size
  476. {
  477.   int i,j;
  478.   PART *part = attachments;
  479.   [browser getNumRows:&i numCols:&j];
  480.   [browser insertRowAt:i];    // create a new row with the name and display
  481.   [[browser cellAt:i :0] setStringValue:name];
  482.   [[browser sizeToCells] display];
  483.   if (part) {            // if prior attachments, add at the end
  484.     while (part->next) part = part->next;
  485.     part = (part->next = mail_newbody_part ());
  486.   }
  487.   else {            // first time, instantiate new, open window
  488.     part = attachments = mail_newbody_part ();
  489.     [[attachmentPanel attachWindow:window] orderFront:self];
  490.   }
  491.   part->body.encoding = ENCBASE64;
  492.                 // install the file data
  493.   part->body.contents.text = rfc822_binary (data,size,&part->body.size.bytes);
  494.   return part;
  495. }
  496.  
  497. // Attach sound from microphone into the message
  498.  
  499. #define MAXSIZE 120.0*SND_RATE_CODEC
  500.  
  501. - attachSound:sender
  502. {
  503.   char *t;
  504.   char tmp[TMPLEN];
  505.   int i,j;
  506.   PART *part;
  507.   SNDSoundStruct *snd;
  508.   NXStream *file;
  509.   if (sound) {            // voice mail in progress?
  510.     SNDStop (69);        // stop recording sounds
  511.     SNDWait (69);        // wait for it to be done 
  512.     strcpy (tmp,"/tmp/audioXXXXXX.snd");
  513.     NXGetTempFilename (tmp,strchr (tmp,'X') - tmp);
  514.     if ((i = SNDWriteSoundfile (tmp,sound)) ||
  515.     !(file = NXMapFile (tmp,NX_READONLY)))
  516.       NXRunAlertPanel ("Sound attachment failed",i ? SNDSoundError (i) :
  517.                "Can't re-read sound data",NIL,NIL,NIL);
  518.     else {            // read back the sound data
  519.       NXGetMemoryBuffer (file,&t,&i,&j);
  520.                 // point at header
  521.       snd = (SNDSoundStruct *) t;
  522.       part = [self attachFile:"Voice Mail" data:t + snd->dataLocation
  523.           size:snd->dataSize];
  524.       NXCloseMemory (file,NX_FREEBUFFER);
  525.       if (part) {
  526.     part->body.type = TYPEAUDIO;// set as audio type, basic subtype
  527.     part->body.subtype = cpystr ("BASIC");
  528.     part->body.description = cpystr ("voice mail");
  529.       }
  530.     }
  531.     unlink (tmp);        // flush the file
  532.     SNDFree (sound);        // flush the sound
  533.     sound = NIL;
  534.   }
  535.                 // start new voice mail, alloc & record
  536.   else if (NXRunAlertPanel ("Record Voice","Hit Voice Mail button when done",
  537.                 NIL,"Cancel",NIL) &&
  538.        ((i =SNDAlloc(&sound,MAXSIZE,SND_FORMAT_MULAW_8,SND_RATE_CODEC,1,0))
  539.         || (i = SNDStartRecording (sound,69,1,0,NIL,NIL))))
  540.     NXRunAlertPanel ("Sound record failed",SNDSoundError (i),NIL,NIL,NIL);
  541.   return self;
  542. }
  543.  
  544. // Delete an attachment
  545.  
  546. - deleteAttachment:sender
  547. {
  548.   int i = 0;
  549.   PART *part = attachments;
  550.   PART *prev = NIL;
  551.   PART *die;
  552.   while (part) {        // for each part
  553.                 // want to remove this one?
  554.     if ([[browser cellAt:i : 0] state]) {
  555.       die = part;        // yes, get part that will die
  556.       part = die->next;        // replace it on list with tail
  557.       die->next = NIL;        // cut tail from dying part
  558.                 // cut dying part from head
  559.       if (prev) prev->next = part;
  560.       else attachments = part;
  561.                 // flush dying part
  562.       mail_free_body_part (&die);
  563.                 // remove display from browser
  564.       [[[browser removeRowAt:i andFree:T] sizeToCells] display];
  565.     }
  566.     else {            // preserve this part
  567.       prev = part;        // note as as head
  568.       part = part->next;    // current is now tail
  569.       i++;            // bump count
  570.     }
  571.                 // flush attachments if none left
  572.     if (!attachments) [attachmentPanel orderOut:self];
  573.   }
  574.   return self;
  575. }
  576.  
  577. // Reformat selected text
  578.  
  579. - reformat:sender
  580. {
  581.   BOOL dot = NIL;
  582.   int i;
  583.   char *text,*src,*dst;
  584.   NXSelPt s,e;
  585.   [bodyView getSel:&s :&e];    // get current selection
  586.   start = s.cp; end = e.cp;    // get the points as integers
  587.                 // calculate length of selection
  588.   if ((i = 1 + end - start) > 1) {
  589.                 // get enough space for the text in worst case
  590.     text = dst = (char *) fs_get (i*2);
  591.                 // get text from the view
  592.     [bodyView getSubstring:(src = text+i) start:start length:i-1];
  593.     text[i*2-1] = '\0';        // tie off text
  594.     while (*src) {        // go through the entire string
  595.       if (*src == '\n')        // if a newline
  596.     switch (src[1]) {    // sniff at the next character
  597.     case '\n':        // consecutive newlines are never mauled!
  598.       while (*src == '\n') *dst++ = *src++;
  599.       break;        // (don't need to worry about resetting dot)
  600.     case '\t':        // tabs...
  601.     case ' ':        //  and spaces
  602.       *dst++ = *src++;    // are data breaks and not mauled
  603.       break;        // (don't need to worry about resetting dot)
  604.     default:
  605.       *dst++ = ' ';        // change it to a space
  606.       if (dot) *dst++ = ' ';// insert a second space if saw a period
  607.       src++;        // go to next input character
  608.       break;
  609.     }
  610.       else {            // otherwise copy the character
  611.     if (*src != '\015') *dst++ = *src;
  612.     dot = (*src++ == '.');    // remember if last character was a period
  613.       }
  614.     }
  615.     *dst = '\0';        // tie off destination
  616.     [bodyView replaceSel:text];    // replace with reformatted string
  617.     [bodyView display];
  618.   }
  619.   return self;
  620. }
  621.  
  622. // Send the message
  623.  
  624. - send:sender
  625. {
  626.   int win = NIL;
  627.   char hdr[16*TMPLEN];
  628.   unsigned char *text;
  629.   long i;
  630.   struct tm *t;
  631.   struct timeval tv;
  632.   struct timezone tz;
  633.   FILE *file;
  634.   SMTPSTREAM *stream;
  635.   MESSAGECACHE elt;
  636.   BODY *body = mail_newbody ();
  637.                 // get an MTP connection
  638.   if (!(stream = smtp_open ((char **) hostlist,[debugging intValue])))
  639.     NXRunAlertPanel ("Sending Failed",
  640.              "Unable to connect to server - try again",NIL,NIL,NIL);
  641.   else {            // now send it the message
  642.     text = gettext (bodyView);    // get text from view
  643.     if (attachments) {        // set up multipart msg if have attachments
  644.       body->type = TYPEMULTIPART;
  645.       if (text && *text) {    // first content is text
  646.     body->contents.part = mail_newbody_part ();
  647.     body->contents.part->body.contents.text = text;
  648.                 // followed by attachments
  649.     body->contents.part->next = attachments;
  650.       }
  651.                 // otherwise attachments are all there is
  652.       else body->contents.part = attachments;
  653.     }
  654.                 // otherwise simple message
  655.     else body->contents.text = text;
  656.  
  657.                 // send mail
  658.     if (win = smtp_mail (stream,"MAIL",env,body)) {
  659.       // Note: outbox copy does not receive attachments.  Bug or feature?
  660.                 // have an outbox and not remail?
  661.       if (*outbox && !env->remail) {
  662.     if (!(file = fopen (outbox,"a")))
  663.       NXRunAlertPanel ("Open failed","Can't open outbox",NIL,NIL,NIL);
  664.     else {            // get time and timezone poop
  665.       gettimeofday (&tv,&tz);
  666.       t = localtime (&tv.tv_sec);
  667.       elt.day = t->tm_mday; elt.month = t->tm_mon + 1;
  668.       elt.year = t->tm_year - (BASEYEAR - 1900);
  669.       elt.hours = t->tm_hour; elt.minutes = t->tm_min;
  670.       elt.seconds = t->tm_sec;
  671.       i = abs (t->tm_gmtoff/60); elt.zoccident = t->tm_gmtoff < 0;
  672.       elt.zhours = i/60; elt.zminutes = i % 60;
  673.                 // calculate header
  674.       rfc822_header (hdr,env,attachments ? &body->contents.part->body :
  675.              body);
  676.                 // blat it to the file
  677.       append_msg (file,env->from,&elt,hdr,text);
  678.       fclose (file);    // close off the file
  679.     }
  680.       }
  681.     }
  682.     else {            // otherwise report recipient errors
  683.       [[[self rcptErr:env->to] rcptErr:env->cc] rcptErr:env->bcc];
  684.       NXRunAlertPanel ("Sending Failed",stream->reply,NIL,NIL,NIL);
  685.     }
  686.     smtp_close (stream);    // nuke the stream in any case
  687.     if (attachments) {        // take attachments off before freeing body
  688.       if (body->contents.part == attachments) body->contents.part = NIL;
  689.       else body->contents.part->next = NIL;
  690.     }
  691.     mail_free_body (&body);    // nuke body
  692.   }
  693.                 // clear up if won
  694.   return win ? [self exit:self] : self;
  695. }
  696.  
  697. // Exit after success (intended to be overridden by ReplyWindow subclass)
  698.  
  699. - exit:sender
  700. {
  701.   [sender windowWillClose:self];// do abort actions
  702.   [window close];        // close the window
  703.   window = NIL;
  704.   return self;
  705. }
  706.  
  707.  
  708. // Window is closing
  709.  
  710. - windowWillClose:sender
  711. {
  712.   [attachmentPanel close];    // nuke attachment panel
  713.   attachmentPanel = NIL;
  714.   mail_free_envelope (&env);    // flush the message
  715.                 // flush attachments
  716.   mail_free_body_part (&attachments);
  717.   if (sound) {            // unterminated voice mail...
  718.     SNDStop (0);        // stop recording sounds
  719.     SNDWait (0);        // wait for it to be done
  720.     SNDFree (sound);        // flush the sound
  721.   }
  722.   return self;
  723. }
  724.  
  725.  
  726. // Window is resizing
  727.  
  728. - windowWillResize:sender toSize:(NXSize *) frameSize
  729. {
  730.                 // constrain height to minimum
  731.   frameSize->height = max (frameSize->height,60+NX_HEIGHT (&envelopeFrame));
  732.   return self;
  733. }
  734.  
  735.  
  736. // Window moved
  737.  
  738. - windowDidMove:sender
  739. {
  740.                 // make attachment panel track this window
  741.   [attachmentPanel attachWindow:window];
  742.   return self;
  743. }
  744.  
  745.  
  746. // mdd : use an XText for the window's text fields
  747.  
  748. - windowWillReturnFieldEditor:sender toObject:client
  749. {
  750.     return [XText newFieldEditorFor:sender
  751.                     initialAction:xtext_action
  752.                     estream:nil];
  753. }
  754.  
  755. // Note our debugging switch
  756.  
  757. - setDebugging:anObject
  758. {
  759.   debugging = anObject;        // note our debug switch
  760.   [debugging setIntValue:debug];// set its initial value
  761.   return self;
  762. }
  763.  
  764.  
  765. // Report error in recipient list
  766.  
  767. - rcptErr:(ADDRESS *) adr
  768. {
  769.   char tmp[TMPLEN];
  770.   if (adr) do if (adr->error) {    // if recipient had a problem
  771.       sprintf (tmp,"%s@%s: %s",adr->mailbox,adr->host,adr->error);
  772.                 // log it (use telemetry window if one)
  773.       mm_log (tmp,(lockedread ? ERROR : WARN));
  774.     } while (adr = adr->next);    // try next recipient
  775.   return self;
  776. }
  777.  
  778.  
  779. @end
  780.