home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / imap-3.0 / MailManager / ReadWindow.m < prev    next >
Encoding:
Text File  |  1993-02-10  |  31.0 KB  |  1,117 lines

  1. /*
  2.  * Program:    Distributed Electronic Mail Manager (ReadWindow object)
  3.  *
  4.  * Author:    Mark Crispin/Mike Dixon, Xerox PARC
  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:    11 February 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 ReadWindow
  41.  
  42. // Create a new ReadWindow
  43.  
  44. + new
  45. {
  46.   self = [super new];        // instantiate ourselves
  47.   [NXApp loadNibSection:"ReadWindow.nib" owner:self withNames:NIL];
  48.   handle = NIL;            // no handle initially
  49.   sequence = NIL;        // no sequence initially
  50.   return self;
  51. }
  52.  
  53.  
  54. // Note our messageView
  55.  
  56. - setMessageView:anObject
  57. {
  58.   NXRect frame;            // note our view and get its frame
  59.   [(messageView = [anObject docView]) getFrame:&frame];
  60.   [messageView setInitialAction: read_action];        // mdd
  61.                 // reset its font
  62.   [messageView renewFont:defaultfont text:"" frame:&frame tag:0];
  63.   [messageView setEditable:NO];    // mdd
  64.   return self;
  65. }
  66.  
  67.  
  68. // Note our window
  69.  
  70. - setWindow:anObject
  71. {
  72.   window = anObject;        // note our window
  73.   [window setDelegate:self];    // we're the window's delegate
  74.   return self;
  75. }
  76.  
  77. // Non-interface methods
  78.  
  79.  
  80. // Note calling window message items view
  81.  
  82. - setMessageItems:anObject
  83. {
  84.   messageItems = anObject;    // note caller's message items view
  85.   return self;
  86. }
  87.  
  88.  
  89. // Note our MAIL handle
  90.  
  91. - setHandle:(MAILHANDLE *) h
  92. {
  93.   mail_free_handle (&handle);    // flush any old handle
  94.   handle = h;            // note MAIL handle
  95.   return self;
  96. }
  97.  
  98. // Set message sequence
  99.  
  100. - setSequence:(SEQUENCE *) seq
  101. {
  102.   SEQUENCE *s;
  103.   if (s = sequence) {        // find head of old sequence list
  104.     while (s->previous) s = s->previous;
  105.     do {
  106.       mail_free_lelt (&s->lelt);// flush the cache entry in case nuked
  107.       sequence = s->next;    // get the next in the list
  108.       fs_give ((void **) &s);    // now free that sequence
  109.     } while (s = sequence);    // run down the sequence list
  110.   }
  111.   return [self moveSequence:seq];
  112. }
  113.  
  114.  
  115. // Move message sequence
  116.  
  117. - moveSequence:(SEQUENCE *) seq
  118. {
  119.   int m;
  120.   id brw;
  121.   if (sequence = seq) {        // set new sequence, update display if non-NIL
  122.     if (lockedread &&        // highlight browser message if locked read
  123.     (seq->lelt->elt.msgno == [[(brw = [messageItems docView])
  124.                  cellAt:(m = seq->lelt->elt.msgno - 1) :0] msgno]))
  125.       [[brw selectCellAt:m :0] scrollCellToVisible: m :0];
  126.     [self updateTitle];        // show the first message's title
  127.     [self displayMessage:self];    // now display the message
  128.     [self updateTitle];        // update the window's title now that read
  129.     [window makeKeyAndOrderFront:self];    // reopen window in case taken out
  130.                                         // mdd: make key to enable keys
  131.   }
  132.   return self;
  133. }
  134.  
  135. // Update window title after action taken to change status
  136.  
  137. - updateTitle
  138. {
  139.   char text[TMPLEN];
  140.   MAILSTREAM *stream = mail_stream (handle);
  141.                 // stream and message must be valid
  142.   if (stream && sequence->lelt->elt.msgno) {
  143.                 // get the window's title
  144.     headerline (text,stream,&sequence->lelt->elt);
  145.     [window setTitle:text];    // set the title
  146.                 // update primary window
  147.     [getstreamprop (stream)->window updateTitle];
  148.   }
  149.                 // update Flagged switch
  150.   [flagged setIntValue:sequence->lelt->elt.flagged];
  151.                 // update Deleted switch
  152.   [deleted setIntValue:sequence->lelt->elt.deleted];
  153.                 // update Seen switch
  154.   [seen setIntValue:sequence->lelt->elt.seen];
  155.                 // update Answered switch
  156.   [answered setIntValue:sequence->lelt->elt.answered];
  157.   return self;
  158. }
  159.  
  160. // Message display operations
  161.  
  162.  
  163. // Note our display
  164.  
  165. - setDisplay:anObject
  166. {
  167.                 // set according to the default
  168.   [(display = anObject) selectCellAt:literaldisplay :0];
  169.   return self;
  170. }
  171.  
  172.  
  173. // Note attachment panel
  174.  
  175. - setAttachmentPanel:anObject
  176. {
  177.   attachmentPanel = anObject;    // note attachment panel
  178.   return self;
  179. }
  180.  
  181.  
  182. // Note attachment browser
  183.  
  184. - setAttachmentView:anObject
  185. {
  186.   NXSize size;
  187.   NXRect frame;
  188.   [anObject getFrame:&frame];    // get frame of attachments view
  189.                 // create a scroll view
  190.   [(attachmentView = [ScrollView newFrame:&frame]) setBorderType:NX_BEZEL];
  191.                 // only vertical scrolling is required
  192.   [attachmentView setVertScrollerRequired:T];
  193.                 // allow its size to change
  194.   [attachmentView setAutosizing:NX_HEIGHTSIZABLE|NX_WIDTHSIZABLE];
  195.                 // put up the view
  196.   [[anObject superview] replaceSubview:anObject with:attachmentView];
  197.                 // get the size minus the scroll bars
  198.   [attachmentView getContentSize:&size];
  199.                 // set it as such in the browser frames
  200.   NX_WIDTH (&frame) = size.width = 1300.0; NX_HEIGHT (&frame) = size.height;
  201.                 // create the browser
  202.   browser = [Matrix newFrame:&frame mode:NX_RADIOMODE
  203.          cellClass:[ActionCell class] numRows:0 numCols:0];
  204.   size.height = 13.0;        // large enough to hold the text
  205.   [browser setCellSize:&size];    // set size of each cell
  206.   size.width = size.height = 0;    // no intercell spacing
  207.   [browser setIntercell:&size];
  208.   [browser setAutoscroll:T];    // make it autoscrolling
  209.   [[browser setAction:@selector(doAttachment:)] setTarget:self];
  210.                 // put the browser in the scroller and display
  211.   [[attachmentView setDocView:browser] display];
  212.   return self;
  213. }
  214.  
  215. // Here is where we actually display a message
  216.  
  217. - displayMessage:sender
  218. {
  219.   char *s;
  220.   NXRect fr;
  221.   int m = sequence->lelt->elt.msgno;
  222.   int lit = [display selectedRow];
  223.   MAILSTREAM *stream = mail_stream (handle);
  224.   ENVELOPE *env = sequence->lelt->env;
  225.   BODY *body = sequence->lelt->body;
  226.   if (stream && m) {        // must have open stream and not be expunged
  227.                 // get frame, don't update display until ready
  228.     [[messageView getFrame:&fr] setAutodisplay:NIL];
  229.                 // initialize view
  230.     [[messageView renewFont:defaultfont text:"" frame:&fr tag:0] setSel:0 :0];
  231.                 // try getting body now if don't have it yet
  232.     if ((!lit) && !body) env = mail_fetchstructure (stream,m,&body);
  233.                 // not literal, have body, body complex
  234.     if ((!lit) && body && (body->type != TYPETEXT)) {
  235.                 // yes, resize browser for # of attachments
  236.       [[browser renewRows:[self countAttachments:body] cols:1] sizeToCells];
  237.       [self displayAttachment:body prefix:NIL index:0 row:0];
  238.       [browser display];    // display attachments
  239.       [[attachmentPanel attachWindow:window] orderFront:self];
  240.                 // open header in case non-textual attachment
  241.       [[messageView setSel:0 :0] replaceSel:s = filtered_header (env)];
  242.       fs_give ((void **) &s);    // done with header string
  243.                 // do first attachment
  244.       [[browser selectCellAt:0 :0] sendAction];
  245.     }
  246.     else {            // simple message has no attachments
  247.       [attachmentPanel orderOut:self];
  248.       if (lit || !body) {
  249.     [[messageView setSel:0 :0] replaceSel:s = lit ?
  250.      fixnl (cpystr (mail_fetchheader (stream,m))) : filtered_header (env)];
  251.     fs_give ((void **) &s);    // done with header string
  252.     [messageView replaceSel:s = fixnl (cpystr (mail_fetchtext(stream,m)))];
  253.     fs_give ((void **) &s);    // done with text string
  254.       }
  255.       else {            // fake as if had attachments
  256.     unsigned long len;
  257.     s = mail_fetchbody (stream,m,"1",&len);
  258.     [self displayText:(unsigned char *) s length:len type:body->type
  259.      subtype:body->subtype encoding:body->encoding];
  260.       }
  261.     }      
  262.                 // put cursor at start, display text
  263.     [[[messageView setSel:0 :0] display] setAutodisplay:T];
  264.   }
  265.   return self;
  266. }
  267.  
  268. // Display text in the view
  269.  
  270. - displayText:(unsigned char *) s length:(unsigned long) len type:(int) type
  271.    subtype:(char *) sub encoding:(int) enc
  272. {
  273.   char *t;
  274.   void *f = NIL;
  275.   NXRect fr;
  276.   ENVELOPE *env = sequence->lelt->env;
  277.   [messageView getFrame:&fr];    // initialize view
  278.   [messageView renewFont:defaultfont text:"" frame:&fr tag:0];
  279.                 // install header
  280.   [[messageView setSel:0 :0] replaceSel:t = filtered_header (env)];
  281.   fs_give ((void **) &t);    // flush temp
  282.   if (!s) return self;
  283.   switch (enc) {        // if text
  284.   case ENCBASE64:        // 3 in 4 encoding -- someone may use this
  285.     f = s = rfc822_base64 (s,len,&len);
  286.     break;
  287.   case ENCQUOTEDPRINTABLE:    // readable 8-bit
  288.     f = s = (unsigned char *) fixnl ((char *) rfc822_qprint (s,len,&len));
  289.     break;
  290.   case ENC7BIT:            // 7-bit text
  291.   case ENC8BIT:            // 8-bit text
  292.     f = s = (unsigned char *) fixnl (cpystr ((char *) s));
  293.     break;
  294.   case ENCBINARY:        // binary
  295.   default:            // everything else
  296.     break;
  297.   }
  298.   // Should do something about RichText here...
  299.                 // display the text
  300.   [messageView replaceSel:s ? (char *) s :
  301.    "Undecodable text -- use literal to display it"];
  302.   if (f) fs_give ((void **) &f);// flush temp
  303.   return self;
  304. }
  305.  
  306.  
  307. // Count non-multipart body parts
  308.  
  309. - (int) countAttachments:(BODY *) body
  310. {
  311.   int i = 0;
  312.   PART *part;
  313.                 // depending upon body type
  314.   if (body) switch (body->type) {
  315.   case TYPEMULTIPART:        // multiple parts
  316.     for (part = body->contents.part; part; part = part->next)
  317.       i += [self countAttachments:&part->body];
  318.     break;
  319.   case TYPEMESSAGE:        // encapsulated message
  320.     i = [self countAttachments:body->contents.msg.body];
  321.   default:            // all other types have one part
  322.     i++;
  323.     break;
  324.   }
  325.   return i;            // return number of body parts
  326. }
  327.  
  328. // Display an attachment in the browser
  329.  
  330. - (int) displayAttachment:(BODY *) body prefix:(char *) pfx index:(int) i
  331.    row:(int) r
  332. {
  333.   char tmp[TMPLEN];
  334.   char *s = tmp;
  335.   PARAMETER *par;
  336.   PART *part;            // multipart doesn't have a row to itself
  337.   if (body->type == TYPEMULTIPART) {
  338.                 // if not first time, extend prefix
  339.     if (pfx) sprintf (tmp,"%s%d.",pfx,++i);
  340.     else tmp[0] = '\0';
  341.     for (i = 0,part = body->contents.part; part; part = part->next)
  342.       r = [self displayAttachment:&part->body prefix:tmp index:i++ row:r];
  343.   }
  344.   else {            // non-multipart, output oneline descriptor
  345.     if (!pfx) pfx = "";        // dummy prefix if top level
  346.     sprintf (s,"%s%d %s",pfx,++i,body_types[body->type]);
  347.     if (body->subtype) sprintf (s += strlen (s),"/%s",body->subtype);
  348.     if (body->description) sprintf (s += strlen (s)," (%s)",body->description);
  349.     if (par = body->parameter) do
  350.       sprintf (s += strlen (s),";%s=%s",par->attribute,par->value);
  351.     while (par = par->next);
  352.     if (body->id) sprintf (s += strlen (s),", id = %s",body->id);
  353.     switch (body->type) {    // bytes or lines depending upon body type
  354.     case TYPEMESSAGE:        // encapsulated message
  355.     case TYPETEXT:        // plain text
  356.       sprintf (s += strlen (s)," (%lu lines)",body->size.lines);
  357.       break;
  358.     default:
  359.       sprintf (s += strlen (s)," (%lu bytes)",body->size.bytes);
  360.       break;
  361.     }
  362.     [[[browser cellAt:r++ :0] setStringValue:tmp] setTag:(int) body];
  363.                 // encapsulated message?
  364.     if (body->type == TYPEMESSAGE && (body = body->contents.msg.body)) {
  365.       if (body->type == TYPEMULTIPART)
  366.     r = [self displayAttachment:body prefix:pfx index:i-1 row:r];
  367.       else {            // build encapsulation prefix
  368.     sprintf (tmp,"%s%d.",pfx,i);
  369.     r = [self displayAttachment:body prefix:tmp index:0 row:r];
  370.       }
  371.     }
  372.   }
  373.   return r;
  374. }
  375.  
  376. // Process an attachment
  377.  
  378. - doAttachment:sender
  379. {
  380.   int i,j;
  381.   char tmp[TMPLEN];
  382.   NXStream *file = NIL;
  383.   id cell = [browser selectedCell];
  384.   char *t;
  385.   char *s = (char *) [cell stringValue];
  386.   BODY *b = (BODY *) [cell tag];
  387.   PARAMETER *par;
  388.   int m = sequence->lelt->elt.msgno;
  389.   unsigned long len = strchr (s,' ') - s;
  390.   MAILSTREAM *stream = mail_stream (handle);
  391.   SavePanel *ds = [SavePanel new];
  392.   strcpyn (tmp,s,len);        // get attachment ID
  393.   tmp[len] = '\0';
  394.   if (stream && m && (s = mail_fetchbody (stream,m,tmp,&len))) switch(b->type){
  395.   case TYPEAUDIO:        // audio -- hope it's something we can grok!
  396.     if (!(file = [self attachmentStream:(unsigned char *) s length:len
  397.           encoding:b->encoding]))
  398.       mm_log ("Can't open memory stream for audio",ERROR);
  399.     else {            // make name for sound
  400.       strcpy (tmp,"/tmp/audioXXXXXX.snd");
  401.                 // sniff at data
  402.       NXGetMemoryBuffer (file,&s,&i,&j);
  403.                 // look like one of our sound files?
  404.       if (strcmp (b->subtype,"BASIC") && (i > 5) &&
  405.       (*(unsigned long *) s) == SND_MAGIC)
  406.     NXSaveToFile (file,NXGetTempFilename (tmp,strchr (tmp,'X') - tmp));
  407.                 // ugh, assume we can win by prepending header
  408.       else if ((j = open (tmp,O_WRONLY|O_CREAT|O_EXCL,0600)) >= 0) {
  409.     SNDSoundStruct snd;
  410.     snd.magic = SND_MAGIC;    // build header
  411.     snd.dataLocation = sizeof (SNDSoundStruct);
  412.     snd.dataSize = j;
  413.     snd.dataFormat = SND_FORMAT_MULAW_8;
  414.     snd.samplingRate = SND_RATE_CODEC;
  415.     snd.channelCount = 1;
  416.     snd.info[0] = snd.info[1] = snd.info[2] = snd.info[3] = 0;
  417.     write (j,(char *) &snd,sizeof (SNDSoundStruct));
  418.     write (j,s,i);
  419.     close (j);
  420.       }
  421.       if (m = SNDPlaySoundfile (tmp,0))
  422.     NXRunAlertPanel ("Audio playback failed",SNDSoundError(m),NIL,NIL,NIL);
  423.       unlink (tmp);        // flush the file
  424.       if ((m || ([cell mouseDownFlags] & NX_SHIFTMASK)) &&
  425.       [[ds setTitle:"Save audio"] runModal] &&
  426.       NXSaveToFile (file,(char *) [ds filename]))
  427.     mm_log ("Can't write audio",ERROR);
  428.       NXCloseMemory (file,NX_FREEBUFFER);
  429.     }
  430.     break;
  431.  
  432.   case TYPEAPPLICATION:        // random application data
  433.     if (strcmp (b->subtype,"POSTSCRIPT")) {
  434.                 // not PostScript
  435.       if (!(file = [self attachmentStream:(unsigned char *) s length:len
  436.             encoding:b->encoding]))
  437.         mm_log ("Can't open memory stream for binary attachment",ERROR);
  438.       else {            // set up prompt based on type of data
  439.     sprintf (tmp,"Save %s data",b->subtype);
  440.     if (!strcmp (b->subtype,"OCTET-STREAM")) {
  441.       for (t = NIL,par = b->parameter; par && !t; par = par->next)
  442.       if (!strcmp (par->attribute,"TYPE"))
  443.         sprintf (tmp,"Save binary %s data",t = par->value);
  444.     }
  445.     else if (!strcmp (b->subtype,"ODA")) {
  446.       for (t = NIL,par = b->parameter; par && !t; par = par->next)
  447.       if (!strcmp (par->attribute,"PROFILE"))
  448.         sprintf (tmp,"Save ODA profile %s data",t = par->value);
  449.     }
  450.     [ds setTitle:tmp];    // set prompt string
  451.                 // get suggested filename
  452.     for (t = NIL,par = b->parameter; par && !t; par = par->next)
  453.       if (!strcmp (par->attribute,"NAME")) t = par->value;
  454.                 // excise directory parths
  455.     if (t && strchr (t,'/')) t = strrchr (t,'/') + 1;
  456.     if ((t ? [ds runModalForDirectory:[ds directory] file:t] :
  457.          [ds runModal]) && NXSaveToFile (file,(char *) [ds filename]))
  458.       mm_log ("Can't write binary attachment",ERROR);
  459.     NXCloseMemory (file,NX_FREEBUFFER);
  460.       }
  461.       break;
  462.     }
  463.                 // fall through into IMAGE if PostScript
  464.  
  465.   case TYPEIMAGE:        // static image
  466.     if (!(file = [self attachmentStream:(unsigned char *) s length:len
  467.           encoding:b->encoding]))
  468.       mm_log ("Can't open memory stream for image",ERROR);
  469.     else {            // try to run program to display subtype
  470.       id workspace = [Application workspace];
  471.                 // make filename with subtype as extension
  472.       sprintf (tmp,"/tmp/imageXXXXXX.%s",strcmp (b->subtype,"POSTSCRIPT") ?
  473.            lcase (strcpy (tmp+100,b->subtype)) : "ps");
  474.                 // save data to file
  475.       NXSaveToFile (file,NXGetTempFilename (tmp,strchr (tmp,'X') - tmp));
  476.                 // see if file type known to WorkSpace
  477.       i = [workspace getInfoForFile:tmp application:&s type:&t];
  478.                 // run app if valid, have app, and app not Edit
  479.       if ((i != NO) && s && strcmp (s,"Edit"))i = [workspace openTempFile:tmp];
  480.       else i = NO;        // make sure we see it as a failure
  481.       if (i == NO) {        // lost?
  482.     NXRunAlertPanel (NIL,"Unable to display image of type %s",
  483.              NIL,NIL,NIL,b->subtype);
  484.     unlink (tmp);        // flush the file
  485.       }
  486.                 // does user want to save it explicitly?
  487.       if ((i == NO) || ([cell mouseDownFlags] & NX_SHIFTMASK)) {
  488.     sprintf (tmp,"Save image/%s",b->subtype);
  489.     if ([[ds setTitle:tmp] runModal] &&
  490.         NXSaveToFile (file,(char *) [ds filename]))
  491.       mm_log ("Can't write image attachment",ERROR);
  492.       }
  493.                 // close memory file
  494.       NXCloseMemory (file,NX_FREEBUFFER);
  495.     }
  496.     break;
  497.  
  498.   case TYPEMESSAGE:        // encapsulated message
  499.   case TYPETEXT:        // plain text -- display in view
  500.     [self displayText:(unsigned char *) s length:len type:b->type
  501.      subtype:b->subtype encoding:b->encoding];
  502.     break;
  503.   case TYPEVIDEO:        // video
  504.   default:            // random
  505.     if (!(file = [self attachmentStream:(unsigned char *) s length:len
  506.           encoding:b->encoding]))
  507.       mm_log ("Can't open memory stream for attachment",ERROR);
  508.     else {
  509.       sprintf (tmp,"Save %s",body_types[b->type]);
  510.       if ([[ds setTitle:tmp] runModal] &&
  511.       NXSaveToFile (file,(char *) [ds filename]))
  512.     mm_log ("Can't write attachment",ERROR);
  513.       NXCloseMemory (file,NX_FREEBUFFER);
  514.     }
  515.     break;
  516.   }
  517.   return self;
  518. }
  519.  
  520. // Return file stream from attachment
  521.  
  522. - (NXStream *) attachmentStream:(unsigned char *) s length:(unsigned long) len
  523.    encoding:(int) enc
  524. {
  525.   NXStream *file = NXOpenMemory (NIL,0,NX_READWRITE);
  526.   if (file) {            // only if we won
  527.     switch (enc) {        // decode as necessary
  528.     case ENCBASE64:        // 3 in 4 encoding -- most likely case
  529.       s = rfc822_base64 (s,len,&len);
  530.       NXWrite (file,s,len);    // write the binary
  531.       fs_give ((void **) &s);
  532.       break;
  533.     case ENCQUOTEDPRINTABLE:    // 8-bit text
  534.       s = rfc822_qprint (s,len,&len);
  535.       NXWrite (file,s,len);    // write the 8-bit text
  536.       fs_give ((void **) &s);
  537.       break;
  538.     default:            // other cases
  539.       NXWrite (file,s,len);    // just write the data
  540.       break;
  541.     }
  542.     NXFlush (file);        // make sure all data written
  543.   }
  544.   return file;            // return the file
  545. }
  546.  
  547. // We are ordered to close
  548.  
  549. - close
  550. {
  551.   [self windowWillClose:self];    // do cleanup
  552.   [window close];        // close our window
  553.   window = NIL;
  554.   return self;
  555. }
  556.  
  557.  
  558. // Set FreeWhenClosed state for our window
  559.  
  560. - setFreeWhenClosed:(BOOL) flag
  561. {
  562.                 // set the state
  563.   [window setFreeWhenClosed:flag];
  564.   return self;
  565. }
  566.  
  567.  
  568. // Window is closing
  569.  
  570. - windowWillClose:sender
  571. {
  572.   [attachmentPanel close];    // nuke attachment panel
  573.   attachmentPanel = NIL;
  574.   [self setSequence:NIL];    // flush the sequence
  575.   [self setHandle:NIL];        // flush the handle
  576.   return self;
  577. }
  578.  
  579.  
  580. // Window moved
  581.  
  582. - windowDidMove:sender
  583. {
  584.                 // make attachment panel track this window
  585.   [attachmentPanel attachWindow:window];
  586.   return self;
  587. }
  588.  
  589. // Miscellaneous and message motion buttons
  590.  
  591.  
  592. // Open the help panel
  593.  
  594. - help:sender
  595. {
  596.   [NXApp readHelp:sender];
  597.   return self;
  598. }
  599.  
  600.  
  601. // Print message
  602.  
  603. - print:sender
  604. {
  605.                 // print what's in the message view
  606.   [messageView printPSCode:self];
  607.   return self;
  608. }
  609.  
  610.  
  611. // Kill button - delete this message, move to next
  612.  
  613. - kill:sender
  614. {
  615.   [deleted setIntValue:T];    // mark that we want deletion
  616.   [self delete:self];        // now delete the message
  617.                 // move to next message, quit if at end
  618.   if ((![self next:self]) && closefinalkill) [self close];
  619.   return self;
  620. }
  621.  
  622. // Next button - move to next message
  623.  
  624. - next:sender
  625. {
  626.   int i;
  627.   MAILSTREAM *stream = mail_stream (handle);
  628.   SEQUENCE *seq = sequence;
  629.   if (stream) {
  630.     while (seq->next) {        // if there's a next
  631.                 // if not expunged we can use it
  632.       if (seq->next->lelt->elt.msgno) break;
  633.       else seq = seq->next;    // otherwise skip over it
  634.     }
  635.                 // if have a useful message use it
  636.     if (seq->next) [self moveSequence:seq->next];
  637.     else {
  638.                 // get this message number
  639.       i = sequence->lelt->elt.msgno;
  640.                 // if single message sequence, not expunged,
  641.                 //  and not at end of mail file
  642.       if (sequence->previous || (i == 0) || (i >= stream->nmsgs)) {
  643.     mm_log ("No next message",WARN);
  644.     return NIL;
  645.       }
  646.       else {            // we can do motion within the mail file
  647.                 // don't need this elt any more
  648.     mail_free_lelt (&sequence->lelt);
  649.                 // move to next message
  650.     sequence->lelt = mail_lelt (stream,++i);
  651.                 // lock down this elt
  652.     if (!++(sequence->lelt->elt.lockcount))
  653.       fatal ("Elt lock count overflow");
  654.                 // now move to that message
  655.     [self moveSequence:sequence];
  656.       }
  657.     }
  658.   }
  659.   return self;
  660. }
  661.  
  662. // Previous button - move to previous message
  663.  
  664. - previous:sender
  665. {
  666.   int i;
  667.   MAILSTREAM *stream = mail_stream (handle);
  668.   SEQUENCE *seq = sequence;
  669.   if (stream) {
  670.     while (seq->previous) {    // if there's a previous
  671.                 // if not expunged we can use it
  672.       if (seq->previous->lelt->elt.msgno) break;
  673.       else seq = seq->previous;    // otherwise skip over it
  674.     }
  675.                 // if have a useful message use it
  676.     if (seq->previous) [self moveSequence:seq->previous];
  677.     else {
  678.                 // get this message number
  679.       i = sequence->lelt->elt.msgno;
  680.                 // if single message sequence, not expunged,
  681.                 //  and not at top of mail file
  682.       if (sequence->next || (i <= 1)) mm_log ("No previous message",WARN);
  683.       else {            // we can do motion within the mail file
  684.                 // don't need this elt any more
  685.     mail_free_lelt (&sequence->lelt);
  686.                 // move to previous message
  687.     sequence->lelt = mail_lelt (stream,--i);
  688.                 // lock down this elt
  689.     if (!++(sequence->lelt->elt.lockcount))
  690.         fatal ("Elt lock count overflow");
  691.                 // now move to that message
  692.     [self moveSequence:sequence];
  693.       }
  694.     }
  695.   }
  696.   return self;
  697. }
  698.  
  699. // System flag switches
  700.  
  701.  
  702. // Note Answered switch
  703.  
  704. - setAnswered:anObject;
  705. {
  706.   answered = anObject;        // note Answered
  707.   return self;
  708. }
  709.  
  710.  
  711. // Note Flagged switch
  712.  
  713. - setFlagged:anObject;
  714. {
  715.   flagged = anObject;        // note Flagged
  716.   return self;
  717. }
  718.  
  719.  
  720. // Note Deleted switch
  721.  
  722. - setDeleted:anObject;
  723. {
  724.   deleted = anObject;        // note Deleted
  725.   return self;
  726. }
  727.  
  728.  
  729. // Note Seen switch
  730.  
  731. - setSeen:anObject;
  732. {
  733.   seen = anObject;        // note Seen
  734.   return self;
  735. }
  736.  
  737. // Answered switch - set the answered status
  738.  
  739. - answer:sender
  740. {
  741.   char seq[10];
  742.   MAILSTREAM *stream = mail_stream (handle);
  743.                 // stream and message must be valid
  744.   if (stream && sequence->lelt->elt.msgno) {
  745.                 // make string form
  746.     sprintf (seq,"%lu",sequence->lelt->elt.msgno);
  747.                 // only allow clearing this
  748.     if (![answered intValue]) mail_clearflag (stream,seq,"\\Answered");
  749.   }
  750.   return [self updateTitle];
  751. }
  752.  
  753. // mdd: need a method to delete/undelete a message
  754. //        mode is 0=undelete; 1=delete; 2=toggle
  755.  
  756. - deleteMe:(int)mode
  757. {
  758.     BOOL val = ((mode == 2) ? ![deleted intValue] : mode);
  759.  
  760.     [deleted setIntValue:val];
  761.     return [self delete:self];
  762. }
  763.  
  764. // Deleted switch - set the deleted status
  765.  
  766. - delete:sender
  767. {
  768.   char seq[10];
  769.   MAILSTREAM *stream = mail_stream (handle);
  770.                 // stream and message must be valid
  771.   if (stream && sequence->lelt->elt.msgno) {
  772.                 // make string form
  773.     sprintf (seq,"%lu",sequence->lelt->elt.msgno);
  774.                 // set state per the switch
  775.     if ([deleted intValue]) mail_setflag (stream,seq,"\\Deleted");
  776.     else mail_clearflag (stream,seq,"\\Deleted");
  777.   }
  778.   return [self updateTitle];
  779. }
  780.  
  781. // Flag switch - set the flagged status
  782.  
  783. - flag:sender
  784. {
  785.   char seq[10];
  786.   MAILSTREAM *stream = mail_stream (handle);
  787.                 // stream and message must be valid
  788.   if (stream && sequence->lelt->elt.msgno) {
  789.                 // make string form
  790.     sprintf (seq,"%lu",sequence->lelt->elt.msgno);
  791.                 // set state per the switch
  792.     if ([flagged intValue]) mail_setflag (stream,seq,"\\Flagged");
  793.     else mail_clearflag (stream,seq,"\\Flagged");
  794.   }
  795.   return [self updateTitle];
  796. }
  797.  
  798.  
  799. // Seen switch - set the seen status
  800.  
  801. - mark:sender
  802. {
  803.   char seq[10];
  804.   MAILSTREAM *stream = mail_stream (handle);
  805.                 // stream and message must be valid
  806.   if (stream && sequence->lelt->elt.msgno) {
  807.                 // make string form
  808.     sprintf (seq,"%lu",sequence->lelt->elt.msgno);
  809.                 // set state per the switch
  810.     if ([seen intValue]) mail_setflag (stream,seq,"\\Seen");
  811.     else mail_clearflag (stream,seq,"\\Seen");
  812.   }
  813.   return [self updateTitle];
  814. }
  815.  
  816. // Keyword operations
  817.  
  818.  
  819. // Note our Keyword button
  820.  
  821. - setKeyword:anObject
  822. {
  823.   id l = [PullOutMenu new:(keyword = anObject)];
  824.   [l addItem:"Keyword..." action:@selector(keywordSet:)];
  825.   [l addItem:"Clear Keyword..." action:@selector(keywordClear:)];
  826.   return self;
  827. }
  828.  
  829.  
  830. // Set the selected keywords in selected messages
  831.  
  832. - keywordSet:sender
  833. {
  834.   return [self keyword:sender set:T];
  835. }
  836.  
  837.  
  838. // Clear the selected keywords in selected messages
  839.  
  840. - keywordClear:sender
  841. {
  842.   return [self keyword:sender set:NIL];
  843. }
  844.  
  845. // Change keywords
  846.  
  847. - keyword:sender set:(BOOL) set
  848. {
  849.   char seq[10];
  850.   char *keys;
  851.   MAILSTREAM *stream = mail_stream (handle);
  852.                 // stream and message must be valid
  853.   if (stream && sequence->lelt->elt.msgno &&
  854.        (keys = [(getstreamprop (stream))->window getKeywords])) {
  855.                 // make string form of this sequence
  856.     sprintf (seq,"%lu",sequence->lelt->elt.msgno);
  857.                 // set the flag, if we can
  858.     if (set) mail_setflag (stream,seq,keys);
  859.     else mail_clearflag (stream,seq,keys);
  860.     fs_give ((void **) &keys);
  861.   }
  862.   return [self updateTitle];    // update title with new status
  863. }
  864.  
  865. // File operations
  866.  
  867.  
  868. // Note our Copy button
  869.  
  870. - setCopy:anObject
  871. {
  872.   id l = [PullOutMenu new:(copy = anObject)];
  873.   [l addItem:"Copy..." action:@selector(fileCopy:)];
  874.   [l addItem:"Move..." action:@selector(fileMove:)];
  875.   return self;
  876. }
  877.  
  878.  
  879. // Copy message to another mailbox
  880.  
  881. - fileCopy:sender
  882. {
  883.   return [self copy:sender delete:NIL];
  884. }
  885.  
  886.  
  887. // Move (copy + delete) message to another mailbox
  888.  
  889. - fileMove:sender
  890. {
  891.   [self copy:sender delete:T];
  892.   if ((![self next:self]) && closefinalkill) [self close];
  893.   return self;
  894. }
  895.  
  896. // Do the copy or move
  897.  
  898. - copy:sender delete:(BOOL) del
  899. {
  900.   char seq[10];
  901.   char *dest;
  902.   FILE *file;
  903.   unsigned char *text;
  904.   LONGCACHE *lelt = sequence->lelt;
  905.   MAILSTREAM *stream = mail_stream (handle);
  906.                 // get file
  907.   if (stream && lelt->elt.msgno && (dest = [NXApp getFile])) {
  908.     if (*dest == '*') {        // want local copy
  909.       if (!(file = fopen (dest+1,"a")))
  910.     NXRunAlertPanel ("Open failed","Can't open local file",NIL,NIL,NIL);
  911.       else {            // blat it to the file
  912.     text = (unsigned char *)
  913.         cpystr (mail_fetchtext (stream,lelt->elt.msgno));
  914.     append_msg (file,lelt->env ? lelt->env->sender : NIL,&lelt->elt,
  915.             mail_fetchheader (stream,lelt->elt.msgno),text);
  916.     fs_give ((void **) &text);
  917.     fclose (file);        // close off the file
  918.     if (del) {        // if called by move:
  919.                 // mark for deletion
  920.       [deleted setIntValue:T];
  921.       [self delete:self];    // now delete the message
  922.     }
  923.       }
  924.     }
  925.     else {            // want remote copy, make string form of msgno
  926.       sprintf (seq,"%lu",lelt->elt.msgno);
  927.                 // now do the copy
  928.       if (del) mail_move (stream,seq,dest+1);
  929.       else mail_copy (stream,seq,dest+1);
  930.     }
  931.     fs_give ((void **) &dest);    // flush the destination file string
  932.   }
  933.   return [self updateTitle];
  934. }
  935.  
  936. // Message forwarding
  937.  
  938.  
  939. // Note our Forward button
  940.  
  941. - setForward:anObject;
  942. {
  943.   id l = [PullOutMenu new:(forward = anObject)];
  944.   [l addItem:"Forward..." action:@selector(forward:)];
  945.   [l addItem:"Remail..." action:@selector(remail:)];
  946.   return self;
  947. }
  948.  
  949.  
  950. // Forward button - forward a message
  951.  
  952. - forward:sender
  953. {
  954.   char tmp[TMPLEN],t[TMPLEN];
  955.   char *s;
  956.   id compose;
  957.   id body;
  958.   int m = sequence->lelt->elt.msgno;
  959.   ENVELOPE *msg;
  960.   ENVELOPE *env = sequence->lelt->env;
  961.   MAILSTREAM *stream = mail_stream (handle);
  962.   if (stream && m) {        // have to have a stream to do this
  963.     mail_fetchfrom (t,stream,m,FROMLEN);
  964.                 // tie off trailing spaces
  965.     for (s = t+FROMLEN-2; *s == ' '; --s) *s = '\0';
  966.     sprintf (tmp,"[%s: %s]",t,(env && env->subject) ?
  967.          env->subject : "(forwarded message)");
  968.     compose = [NXApp compose:self];
  969.     msg = [compose msg];
  970.     msg->subject = cpystr (tmp);// set up Subject
  971.     [compose updateEnvelopeView];
  972.     body = [compose bodyView];    // get the message text body view
  973.     [body selectAll:sender];    // prepare to initialize text
  974.     [body replaceSel:"\n ** Begin Forwarded Message **\n\n"];
  975.                 // insert message
  976.     [body replaceSel:(s = ([display selectedRow] || !env) ?
  977.      fixnl (cpystr (mail_fetchheader (stream,m))) : filtered_header (env))];
  978.     fs_give ((void **) &s);
  979.     [body replaceSel:(s = fixnl (cpystr (mail_fetchtext (stream,m))))];
  980.     fs_give ((void **) &s);    // don't need space any more
  981.     [body setSel:0:0];        // put cursor at start of message
  982.     [body display];        // update the display
  983.     [compose selectBody];    // now select the appropriate field
  984.   }
  985.   return self;
  986. }
  987.  
  988. // Remail button - remail a message
  989.  
  990. - remail:sender
  991. {
  992.   char *s;
  993.   id compose;
  994.   id body;
  995.   int msgno = sequence->lelt->elt.msgno;
  996.   ENVELOPE *msg;
  997.   MAILSTREAM *stream = mail_stream (handle);
  998.   if (stream && msgno) {    // have to have a stream and msgno to do this
  999.     compose = [NXApp compose:self];
  1000.     msg = [compose msg];
  1001.                 // get original header
  1002.     msg->remail = cpystr (mail_fetchheader (stream,msgno));
  1003.     body = [compose bodyView];    // get the message text body view
  1004.     [body selectAll:sender];    // prepare to initialize text
  1005.     [body replaceSel:(s = fixnl (cpystr (mail_fetchtext (stream,msgno))))];
  1006.     fs_give ((void **) &s);
  1007.     [body setSel:0:0];        // put cursor at start of message
  1008.     [body setEditable:NIL];    // can't change remail text
  1009.     [body display];        // update the display
  1010.     [compose selectBody];    // now select the appropriate field
  1011.   }
  1012.   return self;
  1013. }
  1014.  
  1015. // Message reply operations
  1016.  
  1017.  
  1018.  
  1019. // Note our Reply button
  1020.  
  1021. - setReply:anObject;
  1022. {
  1023.   id l = [PullOutMenu new:(reply = anObject)];
  1024.   [l addItem:"Reply..." action:@selector(replySender:)];
  1025.   [l addItem:"Reply to All..." action:@selector(replyAll:)];
  1026.   return self;
  1027. }
  1028.  
  1029.  
  1030. // Reply to All button - reply to all recipients of message
  1031.  
  1032. - replyAll:sender
  1033. {
  1034.   return [self reply:sender all:T];
  1035. }
  1036.  
  1037.  
  1038. // Reply to Sender button - reply to sender of message
  1039.  
  1040. - replySender:sender
  1041. {
  1042.   return [self reply:sender all:NIL];
  1043. }
  1044.  
  1045. // Reply to message
  1046.  
  1047. - reply:sender all:(BOOL) all
  1048. {
  1049.   char *s,*d;
  1050.   int i = [messageView textLength];
  1051.   MAILSTREAM *stream = mail_stream (handle);
  1052.   d = (s = fs_get (1+i));    // get message we're replying to
  1053.   [messageView getSubstring:s start:0 length:i];
  1054.   s[i] = '\0';            // tie off string
  1055.                 // search for double newline
  1056.   while (*d) if (*d++ == '\n' && *d == '\n') break;
  1057.   if (*d) d++;            // skip over the second one if found it
  1058.   else d = s;            // else use original string
  1059.                 // create a compose window under it
  1060.   [[ReplyWindow new:(stream ? mail_makehandle (stream) : NIL)
  1061.    lelt:sequence->lelt text:d all:all] attachWindow:window];
  1062.   fs_give ((void **) &s);    // flush the text
  1063.   return self;
  1064. }
  1065.  
  1066.  
  1067. @end
  1068.  
  1069. // mdd: support for keyboard message movement
  1070.  
  1071. @interface XText(ReadWindowText)
  1072. - nextMsg:(BOOL)closeAtEnd;
  1073. - prevMsg;
  1074. - deleteMsg:(int)mode;    // mode is 0=undelete; 1=delete; 2=toggle
  1075. @end
  1076.  
  1077. static id my_readWindow(id view) {
  1078.     id readWindow = [[view window] delegate];
  1079.     
  1080.     if (readWindow && [readWindow isKindOf:[ReadWindow class]])
  1081.         return readWindow;
  1082.     else
  1083.         return nil;
  1084. }
  1085.  
  1086. @implementation  XText(ReadWindowText)
  1087.  
  1088. - nextMsg:(BOOL)closeAtEnd
  1089. {
  1090.     id readWindow = my_readWindow(self);
  1091.     
  1092.     if (readWindow)
  1093.         if ((![readWindow next:self]) && closeAtEnd)
  1094.             [readWindow close];
  1095.     return self;
  1096. }
  1097.  
  1098. - prevMsg
  1099. {
  1100.     id readWindow = my_readWindow(self);
  1101.     
  1102.     if (readWindow)
  1103.         [readWindow previous:self];
  1104.     return self;
  1105. }
  1106.  
  1107. - deleteMsg:(int)mode
  1108. {
  1109.     id readWindow = my_readWindow(self);
  1110.     
  1111.     if (readWindow)
  1112.         [readWindow deleteMe:mode];
  1113.     return self;
  1114. }
  1115.  
  1116. @end
  1117.