home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / imap-3.0 / MailManager / MBoxWindow.m < prev    next >
Encoding:
Text File  |  1992-09-28  |  36.2 KB  |  1,388 lines

  1. /*
  2.  * Program:    Distributed Electronic Mail Manager (MBoxWindow 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:    23 February 1989
  13.  * Last Edited:    28 September 1992
  14.  *
  15.  * Copyright 1992 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.  
  39. @implementation MBoxWindow
  40.  
  41. // Create a new MBoxWindow
  42.  
  43. + new
  44. {
  45.   self = [super new];        // create ourselves
  46.                 // get the interface
  47.   [NXApp loadNibSection:"MBoxWindow.nib" owner:self withNames:NIL];
  48.   mailStream = NIL;        // no stream yet
  49.   entryID = NIL;        // no entry ID either
  50.   nmsgs = 0;            // initialize number of messages
  51.   keywordPanel = selectPanel = NIL;
  52.   return self;            // give us back to caller
  53. }
  54.  
  55.  
  56. // Note our window
  57.  
  58. - setWindow:anObject
  59. {
  60.                 // note the window's frame
  61.   [(window = anObject) getFrame:&windowFrame];
  62.   [window setDelegate:self];    // we're the window's delegate
  63.   return self;
  64. }
  65.  
  66. // Create the message items view
  67.  
  68. - setMessageItems:anObject
  69. {
  70.   NXSize size;
  71.   NXRect frame;
  72.   [anObject getFrame:&frame];    // get frame of our message items view
  73.                 // create a scroll view
  74.   [(messageItems = [ScrollView newFrame:&frame]) setBorderType:NX_BEZEL];
  75.                 // only vertical scrolling is required
  76.   [messageItems setVertScrollerRequired:T];
  77.                 // allow its size to change
  78.   [messageItems setAutosizing:NX_HEIGHTSIZABLE|NX_WIDTHSIZABLE];
  79.                 // put up the view
  80.   [[anObject superview] replaceSubview:anObject with:messageItems];
  81.                 // get the size minus the scroll bars
  82.   [messageItems getContentSize:&size];
  83.                 // set it as such in the browser frames
  84.   NX_WIDTH (&frame) = size.width; NX_HEIGHT (&frame) = size.height;
  85.                 // create the browser
  86.   browser = [Matrix newFrame:&frame mode:NX_LISTMODE
  87.          cellClass:[BrowserCell class] numRows:0 numCols:1];
  88.                 // create the zoomer
  89.   zoomer = [Matrix newFrame:&frame mode:NX_LISTMODE
  90.         cellClass:[BrowserCell class] numRows:0 numCols:1];
  91.   size.height = 13.0;        // just tall enough to hold the text
  92.   [browser setCellSize:&size];    // set the size to the minimum
  93.   [zoomer setCellSize:&size];    // set the zoomer size to the minimum
  94.                 // no intercell spacing
  95.   size.width = 0; size.height = 0;
  96.   [browser setIntercell:&size];
  97.   [zoomer setIntercell:&size];
  98.   [browser sizeToCells];    // resize the browser
  99.   [zoomer sizeToCells];        // resize the zoomer
  100.   [browser setAutoscroll:T];    // make it autoscrolling
  101.   [zoomer setAutoscroll:T];    // make it autoscrolling
  102.   [browser setTarget:self];    // make double-click read that message
  103.   [zoomer setTarget:self];    // make zoomer double-click read that message
  104.   if (lockedread) {        // single click reads message if locked
  105.     [browser setAction:@selector(readMessage:)];
  106.     [zoomer setAction:@selector(readMessage:)];
  107.   }
  108.   else {            // else use double click
  109.     [browser setDoubleAction:@selector(readMessage:)];
  110.     [zoomer setDoubleAction:@selector(readMessage:)];
  111.   }
  112.                 // make the browser the scroller's docView
  113.   [messageItems setDocView:browser];
  114.   [messageItems display];    // put up the whole browser
  115.   return self;
  116. }
  117.  
  118. // Non-interface methods
  119.  
  120.  
  121. // Set MAIL stream for window
  122.  
  123. - setStream:(MAILSTREAM *)stream
  124. {
  125.   char tmp[TMPLEN];
  126.   int m = -1;
  127.                 // in case server didn't give us an EXISTS
  128.   int n = (getstreamprop (stream)->nmsgs);
  129.                   // note the stream and its MBoxWindow
  130.   putstreamprop ((mailStream = stream),self);
  131.   [self exists:n];        // note number of messages from streamprop
  132.                 // only do this if can do selections
  133.   if (!lockedread && stream->mailbox) {
  134.     strcpy (tmp,"UNSEEN");    // do search (note: criteria must be writable)
  135.     mail_search (mailStream,tmp);
  136.     [self zoom:self];        // do zoom action
  137.   }
  138.   if (![zooming intValue]) {    // only if not zooming
  139.                 // get browser range and resize
  140.     [[browser getNumRows:&n numCols:&m] sizeToCells];
  141.                 // look for a selected message
  142.     for (m = 0; m < n && ![[browser cellAt:m :0] state]; ++m);
  143.                 // make sure first recent message visible
  144.     [browser scrollCellToVisible:min (n-1,m+19) :0];
  145.   }
  146.   if (*stream->mailbox == '{' || (*stream->mailbox == '*' &&
  147.                   stream->mailbox[1] == '{')) {
  148.     mail_find (stream,"*");    // learn about more mailboxes and BBoards
  149.     if (readbboards) mail_find_bboards (stream,"*");
  150.   }
  151.   if (interval)            // set up periodic event to wake us up
  152.     entryID = DPSAddTimedEntry (interval,(DPSTimedEntryProc) mm_wakeup,
  153.                 (void *) self,5);
  154.                 // want automatic message reading?
  155.   if (autoread) [self readMessage:self];
  156.   return self;
  157. }
  158.  
  159. // Update the title and display of the message selection window, pop cursor
  160.  
  161. - updateTitle
  162. {
  163.   char tmp[TMPLEN];
  164.   if (mailStream && mailStream->mailbox) {
  165.     sprintf (tmp,"%s%s -- %lu Messages, %lu Recent",
  166.          [zooming intValue] ? "Zoomed " : "",mailStream->mailbox,
  167.          mailStream->nmsgs,mailStream->recent);
  168.     [browser sizeToCells];    // resize the browser to flush it
  169.     [messageItems display];    // redisplay the beast
  170.   }
  171.   else strcpy (tmp,"<No mailbox open>");
  172.   [window setTitle:tmp];    // set window's title
  173.   return self;
  174. }
  175.  
  176.  
  177. // Return our window
  178.  
  179. - window
  180. {
  181.   return window;        // return our window
  182. }
  183.  
  184. // Recalculate sequence
  185.  
  186. - (int) updateSequence
  187. {
  188.   int f = 0;
  189.   int m,n;
  190.   int msgno,range;
  191.   int beg = 0;
  192.   id browse = [zooming intValue] ? zoomer : browser;
  193.   id cell;
  194.   char *seq = sequence;
  195.   *seq = '\0';            // destroy old sequence poop
  196.                 // get range of this browser
  197.   [browse getNumRows:&n numCols:&m];
  198.                 // for each selected message
  199.   for (m = (range = 0); m < n; ++m) {
  200.     if ([(cell = [browse cellAt:m :0]) state]) {
  201.       msgno = [cell msgno];    // get message number for this cell
  202.       if (range) {        // range in progress?
  203.                 // yes, can we continue it?
  204.     if (msgno == range+1) range = msgno;
  205.     else {            // can't, close off the range if appropriate
  206.       if (range != beg) sprintf (seq,":%d,%d",range,msgno);
  207.       else sprintf (seq,",%d",msgno);
  208.       range = (beg = msgno);// start a new range
  209.     }
  210.       }
  211.       else {            // output this sequence
  212.     if (f) *seq++ = ',';    // start with a comma if not first time
  213.     else f = T;        // first time only once
  214.     sprintf (seq,"%d",msgno);
  215.     range = (beg = msgno);    // start a new range
  216.       }
  217.     }
  218.     else {            // close off the range if there was one
  219.       if (range && (range != beg)) sprintf (seq,":%d",range);
  220.       range = 0;        // no longer in a range
  221.     }
  222.     seq += strlen (seq);    // update the pointer (broken sprintf...)
  223.   }
  224.                 // close off the range if still one
  225.   if (range && (range != beg)) sprintf (seq,":%d",range);
  226.   return (f);            // return flag to caller
  227. }
  228.  
  229. // Methods invoked by MAIL library via MailManager/streamprops
  230.  
  231.  
  232. // Message exists (i.e. there are that many messages in the mailbox)
  233.  
  234. - exists:(int) msgno;
  235. {
  236.   char tmp[TMPLEN];
  237.   char *word;
  238.   int i = msgno - nmsgs;
  239.   if (nmsgs) word = " new ";    // if not first time
  240.   else word = " ";        // first time not "new"
  241.   if (i < 0) {            // this is very very bad
  242.     sprintf (tmp,"Bug: Mailbox shrunk from %d to %d",nmsgs,msgno);
  243.     fatal (tmp);
  244.   }
  245.   switch (i) {            // based on the number
  246.   case 0:            // no output if zero
  247.     return self;
  248.   case 1:            // let's have halfway decent English here...
  249.     sprintf (tmp,"There is 1%smessage in %s",word,mailStream->mailbox);
  250.     break;
  251.   default:
  252.     sprintf (tmp,"There are %d%smessages in %s",i,word,mailStream->mailbox);
  253.     break;
  254.   }
  255.   mm_log (tmp,NIL);        // output the string
  256.   if (nmsgs) NXBeep ();        // beep if a new message
  257.   //  We don't have to worry about locking the elt or getting a handle on
  258.   // the stream, since there is no way that this cell can survive the death
  259.   // of either.  The stream can only go away if this window goes away, and
  260.   // the elt can only go away through the expunged: method below.
  261.   for (; i--; nmsgs++) [[[browser addRow] cellAt:nmsgs :0] setStream:mailStream
  262.                 element:mail_elt (mailStream,nmsgs+1)];
  263. //  Although this would be nice, it can cause mail_fetchstructure () to be
  264. // called, which is a no-no since the stream is locked.
  265. //[browser sizeToCells];    // resize the browser to hold them
  266.   return self;
  267. }
  268.  
  269. // Message matches a search
  270.  
  271. - searched:(int) msgno;
  272. {
  273.   int i = msgno - 1;
  274.   [browser setState:T at:i :0];    // set its state to true
  275.   return self;
  276. }
  277.  
  278.  
  279. // Message expunged
  280.  
  281. - expunged:(int) msgno;
  282. {
  283.                 // flush the message from the browser
  284.   [browser removeRowAt:msgno-1 andFree:T];
  285. //  Although this is neat to watch, it's intolerably slow when you're trying to
  286. // get real work done.  What's more, it might cause mail_fetchstructure () to
  287. // be called, which is a no-no since the stream is locked.  Consequently, the
  288. // expunge: method does this, once after all the expunges are done
  289. //[browser sizeToCells];    // resize the browser to flush it
  290.   nmsgs--;            // decrement number of messages
  291.   return self;
  292. }
  293.  
  294. // Bottom row buttons
  295.  
  296.  
  297. // Check button - check for new messages
  298.  
  299. - check:sender
  300. {
  301.   mail_check (mailStream);    // check for new messages
  302.   return [self updateTitle];    // update title, pop cursor, return
  303. }
  304.  
  305.  
  306. // No-op - ping connection to see if alive
  307.  
  308. - noop:sender
  309. {
  310.   if (mailStream && !mailStream->lock) {
  311.                 // update title, pop cursor
  312.     if (mail_ping (mailStream)) [self updateTitle];
  313.     else {
  314.       [self close];        // do close action
  315.       mail_close (mailStream);    // nuke the stream and associated streamprop
  316.       putstreamprop (mailStream,NIL);
  317.       mailStream = NIL;        // drop pointer to deceased stream
  318.       if (lockedopen) [NXApp close];
  319.     }
  320.   }
  321.   return self;
  322. }
  323.  
  324.  
  325. // Exit button - Expunge and close window
  326.  
  327. - exit:sender
  328. {
  329.                 // expunge the mailbox unless will autoexpunge
  330.   if (sender && !autoexpunge) [self expunge:sender];
  331.   [window close];        // close the window
  332.   [self windowWillClose:sender];// do cleanup actions
  333.   return self;
  334. }
  335.  
  336.  
  337. // Expunge button - destroy deleted messages
  338.  
  339. - expunge:sender
  340. {
  341.   mail_expunge (mailStream);    // expunge the mailbox
  342.   return [self zoom:self];    // rezoom if necessary
  343. }
  344.  
  345. // Close window
  346.  
  347. - close
  348. {
  349.   [selectPanel close];        // sayonara to select panel
  350.                 // flush the wakeup call
  351.   if (entryID) DPSRemoveTimedEntry (entryID);
  352.   entryID = NIL;        // drop pointer to it
  353.                 // nuke a locked read window too
  354.   if (lockedread) [[reader setFreeWhenClosed:T] close];
  355.   reader = NIL;            // drop its pointer
  356.   [window close];        // close the window
  357.   return self;
  358. }
  359.  
  360.  
  361. // Window is closing
  362.  
  363. - windowWillClose:sender
  364. {
  365.   window = NIL;            // this window is dying
  366.   [self close];            // do close action
  367.   if (mailStream) {        // if still a stream
  368.                 // do autoexpunge if appropriate
  369.     if (autoexpunge &&
  370.     NXRunAlertPanel ("Expunge?","Destroy deleted messages?",NIL,"No",NIL))
  371.       mail_expunge (mailStream);
  372.     mail_close (mailStream);    // close the MAIL stream
  373.                 // and drop the MBoxWindow streamprop
  374.     putstreamprop (mailStream,NIL);
  375.     mailStream = NIL;        // drop pointer to deceased stream
  376.   }
  377.   if (lockedopen) [NXApp close];// close the mailbox
  378.   return self;
  379. }
  380.  
  381. // Window moved
  382.  
  383. - windowDidMove:sender
  384. {
  385.                 // make select panel track this window
  386.   [selectPanel attachWindow:window];
  387.   return self;
  388. }
  389.  
  390.  
  391. // Window is resizing
  392.  
  393. - windowWillResize:sender toSize:(NXSize *) frameSize
  394. {
  395.                 // constrain width to maximum
  396.   frameSize->width = min (frameSize->width,NX_WIDTH (&windowFrame));
  397.   return self;
  398. }
  399.  
  400. // Mailbox switches
  401.  
  402.  
  403. // Note our debugging switch
  404.  
  405. - setDebugging:anObject
  406. {
  407.   debugging = anObject;        // note our debug switch
  408.   [debugging setIntValue:debug];// set its initial value
  409.   return self;
  410. }
  411.  
  412.  
  413. // Debug switch - set the protocol telemetry status
  414.  
  415. - debug:sender
  416. {
  417.                 // do debug or no debug
  418.   if ([debugging intValue]) mail_debug (mailStream);
  419.   else mail_nodebug (mailStream);
  420.   return self;
  421. }
  422.  
  423.  
  424. // Note our zooming switch
  425.  
  426. - setZooming:anObject
  427. {
  428.   zooming = anObject;        // note our zoom switch
  429.                 // set its initial value
  430.   [zooming setIntValue:autozoom];
  431.   return self;
  432. }
  433.  
  434. // Zoom switch - zoom in on selected messages
  435.  
  436. - zoom:sender
  437. {
  438.   int i,j;
  439.   if (mailStream) {        // paranoid
  440.                 // get the current size of the zoomer
  441.     [zoomer getNumRows:&i numCols:&j];
  442.                 // flush the old zoomer rows
  443.     while (i--) [zoomer removeRowAt:i andFree:T];
  444.     if ([zooming intValue]) {    // zooming in?
  445.                 // show the zoomer
  446.       if ([messageItems docView] != zoomer) [messageItems setDocView:zoomer];
  447.       [zoomer lockFocus];    // lock it on it
  448.                 // look for selected messages
  449.       for (i = (j = 0); i < nmsgs; ++i) if ([[browser cellAt:i :0] state]) {
  450.     [zoomer addRow];    // add a row to the zoomer
  451.                 // stuff it with the data
  452.     [[zoomer cellAt:j :0] setStream:mailStream
  453.      element:mail_elt (mailStream,i+1)];
  454.                 // set its state
  455.     [zoomer setState:T at:j :0];
  456.                 // and highlight it
  457.     if (!lockedread) [zoomer highlightCellAt:j++ :0 lit:T];
  458.       }
  459.       [zoomer unlockFocus];
  460.       [zoomer sizeToCells];    // resize the zoomer
  461.     }
  462.     else {            // zooming out, restore the browser
  463.       if ([messageItems docView] != browser) [messageItems setDocView:browser];
  464.       if (!lockedread) {
  465.                 // get the size of the browser
  466.     [browser getNumRows:&i numCols:&j];
  467.     [browser lockFocus];
  468.                 // fix the highlighting in the browser
  469.     for (i = 0; i <nmsgs; ++i)
  470.       [browser highlightCellAt:i :0 lit:[[browser cellAt:i :0] state]];
  471.     [browser unlockFocus];
  472.       }
  473.     }
  474.   }
  475.   return [self updateTitle];    // update title, pop cursor, return
  476. }
  477.  
  478. // Keyword management
  479.  
  480.  
  481. // Note our keyword panel
  482.  
  483. - setKeywordPanel:anObject
  484. {
  485.   keywordPanel = anObject;    // note our keyword panel
  486.   return self;
  487. }
  488.  
  489.  
  490. // Note our keyword scroll view
  491.  
  492. - setKeywordItems:anObject
  493. {
  494.   NXRect frame;
  495.   NXSize size;
  496.   int m = -1;
  497.   id cell = [[ButtonCell newTextCell:"keyword"] setAlignment:NX_LEFTALIGNED];
  498.   [anObject getFrame:&frame];    // get frame of our keywords view
  499.                 // create a scroll view
  500.   [keywordItems = [ScrollView newFrame:&frame] setBorderType:NX_BEZEL];
  501.                 // only vertical scrolling is required
  502.   [keywordItems setVertScrollerRequired:T];
  503.                 // make it the view
  504.   [[anObject superview] replaceSubview:anObject with:keywordItems];
  505.                 // get the size minus the scroll bars
  506.   [keywordItems getContentSize:&size];
  507.                 // set it as such in the keyword browser frame
  508.   NX_WIDTH (&frame) = size.width; NX_HEIGHT (&frame) = size.height;
  509.                 // get frame of keyword items view
  510.   keywords = [Matrix newFrame:&frame mode:NX_LISTMODE prototype:cell
  511.           numRows:0 numCols:1];
  512.   [cell calcCellSize:&size];    // get the minimum cell size
  513.                 // make cells fill the width of the browser
  514.   size.width = NX_WIDTH (&frame);
  515.   [keywords setCellSize:&size];    // set the cell size
  516.                 // no intercell spacing
  517.   size.width = 0; size.height = 0;
  518.   [keywords setIntercell:&size];
  519.                 // add user keywords
  520.   if (mailStream) while (mailStream->user_flags[++m])
  521.     [[keywords addRow] setTitle:mailStream->user_flags[m] at:m :0];
  522.   [[keywords addRow] setTitle:"\\Seen" at:m :0];
  523.   [[keywords addRow] setTitle:"\\Answered" at:++m :0];
  524.   [keywords sizeToCells];    // resize the keyword browser to fit
  525.   [keywords setAutoscroll:T];    // make it autoscrolling
  526.   [keywords setTarget:self];    // make double-click set that keyword
  527.   [keywords setDoubleAction:@selector(keywordOK:)];
  528.                 // make the browser the scroller's docView
  529.   [keywordItems setDocView:keywords];
  530.   [keywordItems display];    // put up the items
  531.   return self;
  532. }
  533.  
  534. // Get keywords
  535.  
  536. - (char *) getKeywords
  537. {
  538.   char tmp[TMPLEN];
  539.   int rows,cols;
  540.   id cell;
  541.   if (!keywordPanel)        // create new keyword panel if none yet
  542.     [NXApp loadNibSection:"KeywordPanel.nib" owner:self withNames:NIL];
  543.                 // put up the keyword panel
  544.   [NXApp runModalFor:keywordPanel];
  545.   if (!OK) return NIL;        // punt if user said so
  546.   tmp[0] = '\0';        // make sure string is empty
  547.                 // get number of keywords
  548.   [keywords getNumRows:&rows numCols:&cols];
  549.   while (rows) {        // sniff through browser
  550.                 // if found a selected keyword
  551.     if ([cell = [keywords cellAt:--rows :0] state]) {
  552.                 // if not first time delimit with space
  553.       if (tmp[0]) strcat (tmp," ");
  554.       else strcat (tmp,"(");    // otherwise open a list
  555.                 // now add the keyword
  556.       strcat (tmp,[cell title]);
  557.     }
  558.   }
  559.   if (!tmp[0]) return NIL;    // did the user give at least one keyword?
  560.   strcat (tmp,")");        // close the list
  561.   return (cpystr (tmp));    // return keyword list
  562. }
  563.  
  564.  
  565. // Cancel keyword changing
  566.  
  567. - keywordCancel:sender
  568. {
  569.   [NXApp stopModal];        // end the modality
  570.   [keywordPanel close];        // close the keyword panel
  571.   OK = NIL;            // not OK
  572.   return self;
  573. }
  574.  
  575.  
  576. // User wants to change keywords
  577.  
  578. - keywordOK:sender
  579. {
  580.   [NXApp stopModal];        // end the modality
  581.   [keywordPanel close];        // close the keyword panel
  582.   OK = T;            // not OK
  583.   return self;
  584. }
  585.  
  586. // Note our Keyword button
  587.  
  588. - setKeyword:anObject
  589. {
  590.   id l = [PullOutMenu new:(keyword = anObject)];
  591.   [l addItem:"Keyword..." action:@selector(keywordSet:)];
  592.   [l addItem:"Clear Keyword..." action:@selector(keywordClear:)];
  593.   return self;
  594. }
  595.  
  596.  
  597. // Set the selected keywords in selected messages
  598.  
  599. - keywordSet:sender
  600. {
  601.   return [self keyword:sender set:T];
  602. }
  603.  
  604.  
  605. // Clear the selected keywords in selected messages
  606.  
  607. - keywordClear:sender
  608. {
  609.   return [self keyword:sender set:NIL];
  610. }
  611.  
  612.  
  613. // Change keywords
  614.  
  615. - keyword:sender set:(BOOL) set
  616. {
  617.   char *keys;
  618.                 // make sure have valid sequence
  619.   if (mailStream && [self updateSequence] && (keys = [self getKeywords])) {
  620.                 // set the flag, if we can
  621.     if (set) mail_setflag (mailStream,sequence,keys);
  622.     else mail_clearflag (mailStream,sequence,keys);
  623.     fs_give ((void **) &keys);
  624.   }
  625.   return [self updateTitle];    // update title, pop cursor, return
  626. }
  627.  
  628. // File copying
  629.  
  630.  
  631. // Note our Copy button
  632.  
  633. - setCopy:anObject
  634. {
  635.   id l = [PullOutMenu new:(copy = anObject)];
  636.   [l addItem:"Copy..." action:@selector(fileCopy:)];
  637.   [l addItem:"Move..." action:@selector(fileMove:)];
  638.   return self;
  639. }
  640.  
  641.  
  642. // Copy selected messages to another mailbox
  643.  
  644. - fileCopy:sender
  645. {
  646.   return [self copy:sender delete:NIL];
  647. }
  648.  
  649.  
  650. // Move (copy + delete) selected messages to another mailbox
  651.  
  652. - fileMove:sender
  653. {
  654.   return [self copy:sender delete:T];
  655. }
  656.  
  657. // Copy selected messages to another mailbox
  658.  
  659. - copy:sender delete:(BOOL) del
  660. {
  661.   char *dest;
  662.   int m,n,i;
  663.   FILE *file;
  664.   ENVELOPE *env;
  665.   BODY *body;
  666.   unsigned char *text;
  667.   id cell;
  668.   id browse = [zooming intValue] ? zoomer : browser;
  669.                 // get file
  670.   if (mailStream && [self updateSequence] && (dest = [NXApp getFile])) {
  671.     if (*dest == '*') {        // want local copy?
  672.                 // yes, open local file
  673.       if (!(file = fopen (dest+1,"a")))
  674.     NXRunAlertPanel ("Open failed","Can't open local file",NIL,NIL,NIL);
  675.       else {            // get range of this browser
  676.     [browse getNumRows:&n numCols:&m];
  677.                 // for each selected message in browser
  678.     for (m = 0; m < n; ++m) if ([(cell = [browse cellAt:m :0]) state]) {
  679.                 // get envelope (must be separate instruction!)
  680.       env = mail_fetchstructure (mailStream,(i = [cell msgno]),&body);
  681.       text = (unsigned char *) cpystr (mail_fetchtext (mailStream,i));
  682.                 // blat it to the file
  683.       append_msg (file,env ? env->sender : NIL,mail_elt (mailStream,i),
  684.               mail_fetchheader (mailStream,i),text);
  685.       fs_give ((void **) &text);
  686.     }
  687.     fclose (file);        // close off the file
  688.                 // if move, delete the messages now
  689.     if (del) [self delete:self];
  690.       }
  691.     }
  692.     else {            // want remote copy
  693.       if (del) mail_move (mailStream,sequence,dest+1);
  694.       else mail_copy (mailStream,sequence,dest+1);
  695.     }
  696.     fs_give ((void **) &dest);    // flush the string
  697.   }
  698.   return [self updateTitle];    // update title, pop cursor, return
  699. }
  700.  
  701. // Close current mailbox and open a new mailbox 
  702.  
  703. - newMailbox:sender
  704. {
  705.                 // autoexpunge if appropriate
  706.   if (mailStream && autoexpunge &&
  707.       NXRunAlertPanel ("Expunge?","Destroy deleted messages?",NIL,"No",NIL))
  708.     mail_expunge (mailStream);
  709.   [NXApp reOpen:mailStream window:self];
  710.   return self;
  711. }
  712.  
  713.  
  714. // Print selected messages
  715.  
  716. - print:sender
  717. {
  718.   int i,m,n;
  719.   char *s;
  720.   id cell;
  721.   id browse = [zooming intValue] ? zoomer : browser;
  722.                 // get current selected messages
  723.   if (mailStream && [self updateSequence]) {
  724.     [NXApp startPrint];        // reset printer
  725.                 // get range of this browser
  726.     [browse getNumRows:&n numCols:&m];
  727.     for (m = 0; m < n; ++m) if ([(cell = [browse cellAt:m :0]) state]) {
  728.       s = fixnl (cpystr (mail_fetchheader (mailStream,(i = [cell msgno]))));
  729.       [NXApp printText:s];    // print header
  730.       fs_give ((void **) &s);
  731.       [NXApp printText:(s = fixnl (cpystr (mail_fetchtext (mailStream,i))))];
  732.       fs_give ((void **) &s);
  733.     }
  734.     [NXApp print];        // print the whole thing
  735.   }
  736.   return [self updateTitle];    // update title, pop cursor, return
  737. }
  738.  
  739.  
  740. // Open help panel
  741.  
  742. - help:sender
  743. {
  744.   [NXApp mboxHelp:sender];
  745.   return self;
  746. }
  747.  
  748. // Message reading
  749.  
  750.  
  751. // Read selected messages
  752.  
  753. - readMessage:sender
  754. {
  755.   int m,n;
  756.   SEQUENCE *new;
  757.   SEQUENCE *seq = NIL;
  758.   SEQUENCE *old = NIL;
  759.   id cell;
  760.   id browse = [messageItems docView];
  761.                 // get range of this browser
  762.   [browse getNumRows:&n numCols:&m];
  763.   for (m = 0; m < n; ++m)    // look for selected message or last if locked
  764.     if ([(cell = [browse cellAt:m :0]) state] || (lockedread && (m == n-1))) {
  765.                 // found one, make a sequence list item
  766.       new = (SEQUENCE *) fs_get (sizeof (SEQUENCE));
  767.                 // get cache element for this message
  768.       new->lelt = mail_lelt (mailStream,[cell msgno]);
  769.       new->previous = old;    // previous is old sequence
  770.       new->next = NIL;        // no next for it yet
  771.       if (old) old->next = new;    // if old, tack new on to old's tail
  772.       else seq = new;        // otherwise, start a new sequence
  773.       old = new;        // this is now the old sequence
  774.       if (!++(old->lelt->elt.lockcount)) fatal ("Elt lock count overflow");
  775.       if (lockedread) break;    // no multi-sequence reads if locked
  776.   }
  777.   if (seq) {            // only if we got a sequence
  778.     if (!(lockedread && reader))// make new reader unless locked and have one
  779.       [(reader = [ReadWindow new]) setFreeWhenClosed:!lockedread];
  780.                 // give it the message handle
  781.     [reader setHandle:(mail_makehandle (mailStream))];
  782.                 // and the view of our message items
  783.     [reader setMessageItems:messageItems];
  784.     [reader setSequence:seq];    // set its sequence
  785.   }
  786.   return [self updateTitle];    // update title, pop cursor, return
  787. }
  788.  
  789. // Message status management
  790.  
  791.  
  792. // Note our Delete button
  793.  
  794. - setDelete:anObject
  795. {
  796.   id l = [PullOutMenu new:(delete = anObject)];
  797.   [l addItem:"Delete" action:@selector(delete:)];
  798.   [l addItem:"Undelete" action:@selector(undelete:)];
  799.   return self;
  800. }
  801.  
  802.  
  803. // Note our Flag button
  804.  
  805. - setFlag:anObject
  806. {
  807.   id l = [PullOutMenu new:(flag = anObject)];
  808.   [l addItem:"Flag" action:@selector(flag:)];
  809.   [l addItem:"Unflag" action:@selector(unflag:)];
  810.   return self;
  811. }
  812.  
  813. // Delete selected messages
  814.  
  815. - delete:sender
  816. {
  817.                 // get current selected messages
  818.   if ([self updateSequence]) mail_setflag (mailStream,sequence,"\\Deleted");
  819.   return [self updateTitle];    // update title, pop cursor, return
  820. }
  821.  
  822.  
  823. // Flag selected messages
  824.  
  825. - flag:sender
  826. {
  827.                 // get current selected messages
  828.   if ([self updateSequence]) mail_setflag (mailStream,sequence,"\\Flagged");
  829.   return [self updateTitle];    // update title, pop cursor, return
  830. }
  831.  
  832.  
  833. // Undelete selected messages
  834.  
  835. - undelete:sender
  836. {
  837.                 // get current selected messages
  838.   if ([self updateSequence]) mail_clearflag (mailStream,sequence,"\\Deleted");
  839.   return [self updateTitle];    // update title, pop cursor, return
  840. }
  841.  
  842.  
  843. // Unflag selected messages
  844.  
  845. - unflag:sender
  846. {
  847.                 // get current selected messages
  848.   if ([self updateSequence]) mail_clearflag (mailStream,sequence,"\\Flagged");
  849.   return [self updateTitle];    // update title, pop cursor, return
  850. }
  851.  
  852. // Message forwarding
  853.  
  854.  
  855. // Note our Forward button
  856.  
  857. - setForward:anObject
  858. {
  859.   id l = [PullOutMenu new:(forward = anObject)];
  860.   [l addItem:"Forward..." action:@selector(forward:)];
  861.   [l addItem:"Remail..." action:@selector(remail:)];
  862.   return self;
  863. }
  864.  
  865.  
  866. // Remail messages
  867.  
  868. - remail:sender
  869. {
  870.   int i,m,n;
  871.   char *s;
  872.   ENVELOPE *msg;
  873.   id cell,compose,body;
  874.   id browse = [zooming intValue] ? zoomer : browser;
  875.                 // get range of this browser
  876.   [browse getNumRows:&n numCols:&m];
  877.                 // get current selected messages
  878.   if (mailStream && [self updateSequence])
  879.                 // compose a remail for each selected message
  880.     for (m = 0; m < n; ++m) if ([(cell = [browse cellAt:m :0]) state]) {
  881.       compose = [NXApp compose:self];
  882.       msg = [compose msg];    // get message structure for it
  883.                 // set remail header
  884.       msg->remail = cpystr (mail_fetchheader (mailStream,(i = [cell msgno])));
  885.       body = [compose bodyView];// get the message text body view
  886.       [body selectAll:sender];    // prepare to initialize text
  887.       [body replaceSel:(s = fixnl (cpystr (mail_fetchtext (mailStream,i))))];
  888.       fs_give ((void **) &s);
  889.       [body setSel:0:0];    // put cursor at start of message
  890.       [body setEditable:NIL];    // can't change remail text
  891.       [body display];        // update the display
  892.       [compose selectBody];    // now select the appropriate field
  893.   }
  894.   return [self updateTitle];    // update title, pop cursor, return
  895. }
  896.  
  897. // Forward messages
  898.  
  899. - forward:sender
  900. {
  901.   char tmp[TMPLEN];
  902.   char *s;
  903.   int i,m,n;
  904.   ENVELOPE *msg;
  905.   ENVELOPE *env;
  906.   BODY *b;
  907.   id cell;
  908.   id body = NIL;
  909.   id compose = NIL;
  910.   id browse = [zooming intValue] ? zoomer : browser;
  911.                 // get range of this browser
  912.   [browse getNumRows:&n numCols:&m];
  913.                 // get current selected messages
  914.   if (mailStream && [self updateSequence]) {
  915.                 // compose a remail for each selected message
  916.     for (m = 0; m < n; ++m) if ([(cell = [browse cellAt:m :0]) state]) {
  917.       env = mail_fetchstructure (mailStream,(i = [cell msgno]),&b);
  918.       if (!compose) {        // make a compose window if first time through
  919.     compose = [NXApp compose:self];
  920.     msg = [compose msg];    // get message structure for it
  921.     tmp[0] = '[';
  922.     mail_fetchfrom (tmp+1,mailStream,[cell msgno],FROMLEN);
  923.                 // tie off trailing spaces
  924.     for (s = tmp+FROMLEN; *s == ' '; --s) *s = '\0';
  925.     if (env && env->subject) sprintf (s + 1,": %s]",env->subject);
  926.     else strcpy (s + 1,"]");
  927.     msg->subject = cpystr (tmp);
  928.                 // update the subject field
  929.     [compose updateEnvelopeView];
  930.                 // get the message text body view
  931.     [body = [compose bodyView] selectAll:sender];
  932.     [body replaceSel:"\n ** Begin Forwarded Message(s) **\n\n"];
  933.       }
  934.                 // insert message
  935.       [body replaceSel:(s = (literaldisplay || !env) ?
  936.        fixnl (cpystr (mail_fetchheader (mailStream,i))) :
  937.        filtered_header (env))];
  938.       fs_give ((void **) &s);
  939.       [body replaceSel:(s = fixnl (cpystr (mail_fetchtext (mailStream,i))))];
  940.       fs_give ((void **) &s);
  941.     }
  942.     if (compose) {        // did we get to compose a message?
  943.       [body setSel:0:0];    // put cursor at start of message
  944.       [body display];        // update the display
  945.       [compose selectBody];    // now select the appropriate field
  946.     }
  947.   }
  948.   return [self updateTitle];    // update title, pop cursor, return
  949. }
  950.  
  951. // Message replying
  952.  
  953.  
  954. // Note our Reply button
  955.  
  956. - setReply:anObject
  957. {
  958.   id l = [PullOutMenu new:(reply = anObject)];
  959.   [l addItem:"Reply..." action:@selector(replySender:)];
  960.   [l addItem:"Reply to All..." action:@selector(replyAll:)];
  961.   return self;
  962. }
  963.  
  964.  
  965. // Reply to reply address of selected messages
  966.  
  967. - reply:sender all:(BOOL) all
  968. {
  969.   int i,m,n;
  970.   id cell;
  971.   BODY *body;
  972.   id browse = [zooming intValue] ? zoomer : browser;
  973.                 // get range of this browser
  974.   [browse getNumRows:&n numCols:&m];
  975.                 // get current selected messages
  976.   if (mailStream && [self updateSequence])
  977.                 // compose a reply for each selected message
  978.     for (m = n - 1; m >= 0; --m) if ([(cell = [browse cellAt:m :0]) state]) {
  979.                 // make sure cache loaded
  980.       mail_fetchstructure (mailStream,(i = [cell msgno]),&body);
  981.       mail_elt (mailStream,i);    // start a reply window
  982.       [ReplyWindow new:mail_makehandle (mailStream)
  983.        lelt:mail_lelt (mailStream,i) text:mail_fetchtext (mailStream,i)
  984.        all:all];
  985.   }
  986.   return [self updateTitle];    // update title, pop cursor, return
  987. }
  988.  
  989.  
  990. // Reply to all (reply address + recipients) of selected messages
  991.  
  992. - replyAll:sender
  993. {
  994.   return [self reply:sender all:T];
  995. }
  996.  
  997.  
  998. // Reply to reply address of selected messages
  999.  
  1000. - replySender:sender
  1001. {
  1002.   return [self reply:sender all:NIL];
  1003. }
  1004.  
  1005. // Message selection
  1006.  
  1007.  
  1008. // Note our select panel
  1009.  
  1010. - setSelectPanel:anObject
  1011. {
  1012.   selectPanel = anObject;    // note our select panel
  1013.   return self;
  1014. }
  1015.  
  1016.  
  1017. // Note our After switch
  1018.  
  1019. - setSelectAfter:anObject
  1020. {
  1021.   selectAfter = anObject;    // note our After switch
  1022.   return self;
  1023. }
  1024.  
  1025.  
  1026. // Note our Answered switch
  1027.  
  1028. - setSelectAnswered:anObject
  1029. {
  1030.   selectAnswered = anObject;    // note our Answered switch
  1031.   return self;
  1032. }
  1033.  
  1034.  
  1035. // Note our Before switch
  1036.  
  1037. - setSelectBefore:anObject
  1038. {
  1039.   selectBefore = anObject;    // note our Before switch
  1040.   return self;
  1041. }
  1042.  
  1043.  
  1044. // Note our Cc text
  1045.  
  1046. - setSelectCc:anObject
  1047. {
  1048.   selectCc = anObject;        // note our Cc text
  1049.   return self;
  1050. }
  1051.  
  1052. // Note our Date2 view
  1053.  
  1054. - setSelectDate2:anObject
  1055. {
  1056.   selectDate2 = anObject;    // note our Date2 view
  1057.                 // note its superview
  1058.   selectDate2Super = [selectDate2 superview];
  1059.                 // make it invisible
  1060.   [selectDate2 removeFromSuperview];
  1061.   [selectPanel display];
  1062.   return self;
  1063. }
  1064.  
  1065.  
  1066. // Note our Date text
  1067.  
  1068. - setSelectDateText:anObject
  1069. {
  1070.   selectDateText = anObject;    // note our DateText text
  1071.                 // blank out the value
  1072.   [selectDateText setStringValue:""];
  1073.   return self;
  1074. }
  1075.  
  1076.  
  1077. // Note our DateText2 text
  1078.  
  1079. - setSelectDateText2:anObject
  1080. {
  1081.   selectDateText2 = anObject;    // note our DateText2 text
  1082.                 // disable the cell
  1083.   [selectDateText2 setEnabled:NIL];
  1084.                 // blank out the value
  1085.   [selectDateText2 setStringValue:""];
  1086.   return self;
  1087. }
  1088.  
  1089. // Note our Deleted switch
  1090.  
  1091. - setSelectDeleted:anObject
  1092. {
  1093.   selectDeleted = anObject;    // note our Deleted switch
  1094.   return self;
  1095. }
  1096.  
  1097.  
  1098. // Note our Flagged switch
  1099.  
  1100. - setSelectFlagged:anObject
  1101. {
  1102.   selectFlagged = anObject;    // note our Flagged switch
  1103.   return self;
  1104. }
  1105.  
  1106.  
  1107. // Note our From text
  1108.  
  1109. - setSelectFrom:anObject
  1110. {
  1111.   selectFrom = anObject;    // note our From text
  1112.   return self;
  1113. }
  1114.  
  1115.  
  1116. // Note our New switch
  1117.  
  1118. - setSelectNew:anObject
  1119. {
  1120.   selectNew = anObject;        // note our New switch
  1121.   return self;
  1122. }
  1123.  
  1124.  
  1125. // Note our Old switch
  1126.  
  1127. - setSelectOld:anObject
  1128. {
  1129.   selectOld = anObject;        // note our Old switch
  1130.   return self;
  1131. }
  1132.  
  1133.  
  1134. // Note our Other text
  1135.  
  1136. - setSelectOther:anObject
  1137. {
  1138.   selectOther = anObject;    // note our Other text
  1139.   return self;
  1140. }
  1141.  
  1142. // Note our Recent switch
  1143.  
  1144. - setSelectRecent:anObject
  1145. {
  1146.   selectRecent = anObject;    // note our Recent switch
  1147.   return self;
  1148. }
  1149.  
  1150.  
  1151. // Note our Retain switch
  1152.  
  1153. - setSelectRetain:anObject
  1154. {
  1155.   selectRetain = anObject;    // note our Retain switch
  1156.   return self;
  1157. }
  1158.  
  1159.  
  1160. // Note our Seen switch
  1161.  
  1162. - setSelectSeen:anObject
  1163. {
  1164.   selectSeen = anObject;    // note our Seen switch
  1165.   return self;
  1166. }
  1167.  
  1168.  
  1169. // Note our Subject text
  1170.  
  1171. - setSelectSubject:anObject
  1172. {
  1173.   selectSubject = anObject;    // note our Subject text
  1174.   return self;
  1175. }
  1176.  
  1177.  
  1178. // Note our Text text
  1179.  
  1180. - setSelectText:anObject
  1181. {
  1182.   selectText = anObject;    // note our Text text
  1183.   return self;
  1184. }
  1185.  
  1186.  
  1187. // Note our Texts form
  1188.  
  1189. - setSelectTexts:anObject
  1190. {
  1191.   selectTexts = anObject;    // note our Text form
  1192.   return self;
  1193. }
  1194.  
  1195. // Note our To text
  1196.  
  1197. - setSelectTo:anObject
  1198. {
  1199.   selectTo = anObject;        // note our To text
  1200.   return self;
  1201. }
  1202.  
  1203.  
  1204. // Note our Unanswered switch
  1205.  
  1206. - setSelectUnanswered:anObject
  1207. {
  1208.   selectUnanswered = anObject;    // note our Unanswered switch
  1209.   return self;
  1210. }
  1211.  
  1212.  
  1213. // Note our Undeleted switch
  1214.  
  1215. - setSelectUndeleted:anObject
  1216. {
  1217.   selectUndeleted = anObject;    // note our Undeleted switch
  1218.   return self;
  1219. }
  1220.  
  1221.  
  1222. // Note our Unflagged switch
  1223.  
  1224. - setSelectUnflagged:anObject
  1225. {
  1226.   selectUnflagged = anObject;    // note our Unflagged switch
  1227.   return self;
  1228. }
  1229.  
  1230.  
  1231. // Note our Unseen switch
  1232.  
  1233. - setSelectUnseen:anObject
  1234. {
  1235.   selectUnseen = anObject;    // note our Unseen switch
  1236.   return self;
  1237. }
  1238.  
  1239. // Select button - bring up message selection panel
  1240.  
  1241. - select:sender
  1242. {
  1243.   if (!selectPanel)        // create new select panel if none yet
  1244.     [NXApp loadNibSection:"SelectPanel.nib" owner:self withNames:NIL];
  1245.                 // attach the select panel to this window
  1246.   if (![selectPanel isVisible]) [selectPanel attachWindow:window];
  1247.   [selectPanel orderFront:self];// put up the select panel
  1248.                 // make sure the date2 field is set up right
  1249.   if ([selectAfter intValue]) [self selectAfter:self];
  1250.   else {
  1251.     if ([selectBefore intValue]) [self selectBefore:self];
  1252.     else [self selectDate:self];
  1253.   }
  1254.   return self;
  1255. }
  1256.  
  1257.  
  1258. // Open select panel
  1259.  
  1260. - selectHelp:sender
  1261. {
  1262.   [NXApp selectHelp:sender];
  1263.   return self;
  1264. }
  1265.  
  1266.  
  1267. // Select messages on a particular date
  1268.  
  1269. - selectDate:sender
  1270. {
  1271.                 // set new title for second date
  1272.   [selectDateText2 setTitle:""];
  1273.                 // and disable the field
  1274.   [selectDateText2 setEnabled:NIL];
  1275.                 // make it invisible
  1276.   [selectDate2 removeFromSuperview];
  1277.   [selectPanel display];
  1278.   return self;
  1279. }
  1280.  
  1281.  
  1282. // Select messages after a particular date
  1283.  
  1284. - selectAfter:sender
  1285. {
  1286.                 // set new title for second date
  1287.   [selectDateText2 setTitle:"Before:"];
  1288.                 // and enable the field
  1289.   [selectDateText2 setEnabled:T];
  1290.                 // make it visible again
  1291.   [selectDate2Super addSubview:selectDate2];
  1292.   [selectPanel display];
  1293.   return self;
  1294. }
  1295.  
  1296.  
  1297. // Select messages before a particular date
  1298.  
  1299. - selectBefore:sender
  1300. {
  1301.   [self selectAfter:self];    // make it visible again
  1302.                 // set proper title for second date
  1303.   [selectDateText2 setTitle:"After:"];
  1304.   [selectDate2 display];    // fix the display
  1305.   return self;
  1306. }
  1307.  
  1308. // Search out the messages the user wants selected
  1309.  
  1310. - selectOK:sender
  1311. {
  1312.   char tmp[TMPLEN*2];
  1313.   const char *s;
  1314.   int i;
  1315.   int cnt = 0;
  1316.   if (![selectRetain intValue])    // deselect all unless want retention
  1317.    for (i = 0; i < nmsgs; ++i) [browser setState:NIL at:i :0];
  1318.   strcpy (tmp,"All");        // initialize the search microprogram
  1319.                 // status selection criteria
  1320.   if ([selectAnswered intValue]) strcat (tmp," Answered");
  1321.   if ([selectDeleted intValue]) strcat (tmp," Deleted");
  1322.   if ([selectFlagged intValue]) strcat (tmp," Flagged");
  1323.   if ([selectNew intValue]) strcat (tmp," New");
  1324.   if ([selectOld intValue]) strcat (tmp," Old");
  1325.   if ([selectRecent intValue]) strcat (tmp," Recent");
  1326.   if ([selectSeen intValue]) strcat (tmp," Seen");
  1327.   if ([selectUnanswered intValue]) strcat (tmp," Unanswered");
  1328.   if ([selectUndeleted intValue]) strcat (tmp," Undeleted");
  1329.   if ([selectUnflagged intValue]) strcat (tmp," Unflagged");
  1330.   if ([selectUnseen intValue]) strcat (tmp," Unseen");
  1331.                 // date selection criteria
  1332.   if ((s = [selectDateText stringValue]) && strlen (s)) {
  1333.     if ([selectAfter intValue]) {
  1334.       selstr (tmp," Since",s);
  1335.       if ((s = [selectDateText2 stringValue]) && strlen (s))
  1336.     selstr (tmp," Before", s);
  1337.     }
  1338.     else {
  1339.       if ([selectBefore intValue]) {
  1340.           selstr (tmp," Before",s);
  1341.     if ((s = [selectDateText2 stringValue]) && strlen (s))
  1342.       selstr (tmp," Since", s);
  1343.       }
  1344.       else selstr (tmp," On",s);
  1345.     }
  1346.   }
  1347.                 // various text-based selection criteria
  1348.   if ((s = [selectFrom stringValue]) && strlen (s)) selstr (tmp," From",s);
  1349.   if ((s = [selectTo stringValue]) && strlen (s)) selstr (tmp," To",s);
  1350.   if ((s = [selectCc stringValue]) && strlen (s)) selstr (tmp," cc",s);
  1351.   if ((s = [selectSubject stringValue]) && strlen (s))
  1352.     selstr (tmp," Subject",s);
  1353.   if ((s = [selectText stringValue]) && strlen (s)) selstr (tmp," Body",s);
  1354.                 // finally append any additional criteria
  1355.   if ((s = [selectOther stringValue]) && strlen (s)) {
  1356.     strcat (tmp," ");        // lead with a space
  1357.     strcat (tmp,s);        // and then what user says
  1358.   }
  1359.  
  1360.   mail_search (mailStream,tmp);    // do the search
  1361.                 // count the number of selected messages
  1362.   for (i = 0; i < nmsgs; ++i) if ([[browser cellAt:i :0] state]) cnt++;
  1363.   switch (cnt) {        // report it
  1364.   case 0:            // nothing selected
  1365.     sprintf (tmp,"No messages selected");
  1366.     break;
  1367.   case 1:            // let's have halfway decent English here...
  1368.     sprintf (tmp,"1 message selected");
  1369.     break;
  1370.   default:
  1371.     sprintf (tmp,"%d messages selected",cnt);
  1372.     break;
  1373.   }
  1374.   mm_log (tmp,NIL);        // output the string
  1375.   return [self zoom:self];    // rezoom if necessary
  1376. }
  1377.  
  1378.  
  1379. // Dummy method to get out of text fields
  1380.  
  1381. - selectNull:sender
  1382. {
  1383.   return self;
  1384. }
  1385.  
  1386.  
  1387. @end
  1388.