home *** CD-ROM | disk | FTP | other *** search
/ Otherware / Otherware_1_SB_Development.iso / mac / util / comm / news102.sit / NewsWatcher / source / newsprocess.c < prev    next >
Text File  |  1991-04-03  |  46KB  |  1,929 lines

  1. /*----------------------------------------------------------
  2. #
  3. #    NewsWatcher    - Macintosh NNTP Client Application
  4. #
  5. #    Written by Steven Falkenburg
  6. #    ⌐1990 Apple Computer, Inc.
  7. #
  8. #-----------------------------------------------------------
  9. #
  10. #    newsprocess.c
  11. #
  12. #    This code module contains miscellaneous routines
  13. #    called by many of the other code segments.
  14. #    The memory management routines and status window
  15. #    routines are contained in this module.
  16. #
  17. #-----------------------------------------------------------*/
  18.  
  19. #pragma segment newsprocess
  20.  
  21. #include "compat.h"
  22.  
  23. #ifdef PROTOS
  24.  
  25. #include <Types.h>
  26. #include <QuickDraw.h>
  27. #include <Fonts.h>
  28. #include <Windows.h>
  29. #include <Menus.h>
  30. #include <TextEdit.h>
  31. #include <Dialogs.h>
  32. #include <OSUtils.h>
  33. #include <Desk.h>
  34. #include <ToolUtils.h>
  35. #include <OSEvents.h>
  36. #include <Lists.h>
  37. #include <CursorCtl.h>
  38. #include <Packages.h>
  39. #include <Errors.h>
  40. #include <CType.h>
  41. #include <Strings.h>
  42. #endif
  43.  
  44. #include <StdIO.h>
  45. #include <String.h>
  46.  
  47. #include "nntp.h"
  48. #include "newsprocess.h"
  49. #include "userint.h"
  50. #include "commands.h"
  51. #include "miscstuff.h"
  52. #include "netstuff.h"
  53.  
  54. #ifdef NNTPNEWS
  55. #include "NNTPLow.h"
  56. #else
  57. #include "HFSNTPLow.h"
  58. #endif
  59.  
  60.  
  61. /*    InitSubjectList is called to set up an empty subject list
  62.     data structure.  The subjects are stored in a handle, which 
  63.     is dynamically grown and locked.
  64. */
  65.  
  66. void InitSubjectList(TwindowInfo *theInfo)
  67. {
  68.     theInfo->data2 = (unsigned long)MyNewHandle(1);
  69.     HLock((Handle)theInfo->data2);
  70.     theInfo->numSubjects = 0;
  71. }
  72.  
  73.  
  74. /*    AddToSubjectList gets a range of messages from the NNTP server and puts
  75.     them in the subject list.  The data structure is grown accordingly.
  76. */
  77.  
  78. void AddToSubjectList(TwindowInfo *theInfo,char *newsGroup,long first,long last)
  79. {
  80.     ListHandle theList;
  81.     long i,numMessages;
  82.     Cell theCell,oldCell;
  83.     TSubject *theSubjects;
  84.     Str255 tmpStr;
  85.     extern TPrefRec gPrefs;
  86.     
  87.     theList = (ListHandle) theInfo->data;
  88.     
  89.     /* get max specified msgs */
  90.     
  91.     if (last-first > gPrefs.maxFetch)
  92.         first = last-gPrefs.maxFetch;
  93.         
  94.     HUnlock((Handle)theInfo->data2);
  95.     MySetHandleSize((Handle)theInfo->data2,GetHandleSize((Handle)theInfo->data2)+(sizeof(TSubject)*(last-first+1)));
  96.     if (MyMemErr() != noErr) {
  97.         MoveHHi((Handle)theInfo->data2);
  98.         HLock((Handle)theInfo->data2);
  99.         return;
  100.     }
  101.     MoveHHi((Handle)theInfo->data2);
  102.     HLock((Handle)theInfo->data2);
  103.     theSubjects = (TSubject *) *((Handle)theInfo->data2);
  104.             
  105.     LDoDraw(false,theList);
  106.     if (GetMessages(newsGroup,first,last,&theSubjects[theInfo->numSubjects],&numMessages,"SUBJECT") == noErr) {
  107.         StatusWindow("Building Subject List...",-1);
  108.         GiveTime(0);
  109.         for (i=theInfo->numSubjects; i<numMessages+theInfo->numSubjects; i++) {
  110.             SetPt(&theCell,0,0);
  111.             oldCell = theCell;
  112.             strcpy((char *)tmpStr,theSubjects[i].name);
  113.             tmpStr[strlen((char *)tmpStr)+1] = 0xff;
  114.             DisposPtr(theSubjects[i].name);
  115.             
  116.             if (!gPrefs.mostRecentFirst) {
  117.                 while (LSearch(tmpStr,strlen((char *)tmpStr)+2,SubjectCompare,&theCell,theList)) {
  118.                     theCell.v++;
  119.                     oldCell = theCell;
  120.                     GiveTime(0);
  121.                 }
  122.                 if (oldCell.v != 0)
  123.                     theCell = oldCell;
  124.             }
  125.             else
  126.                 if (!LSearch(tmpStr,strlen((char *)tmpStr)+2,SubjectCompare,&theCell,theList))
  127.                     theCell.v = 0;
  128.             
  129.             LAddRow(1,theCell.v,theList);
  130.             LSetCell(tmpStr,strlen((char *)tmpStr)+2,theCell,theList);
  131.             StatusWindow("Building Subject List...",(short)(((float)(i+1)/(float)(numMessages+theInfo->numSubjects))*100));
  132.             GiveTime(0);
  133.         }
  134.     }
  135.     LDoDraw(true,theList);
  136.     theInfo->numSubjects += numMessages;
  137.     
  138.     SetCursor(&QDARROW);
  139. }
  140.  
  141.  
  142. /*    SubjectCompare is used to determine where to insert a new subject string
  143.     into an existing list of subjects.  The Re: and article number are stripped,
  144.     and the subject strings are then compared.
  145. */
  146.  
  147. pascal short SubjectCompare(char *aPtr,char *bPtr,short aLen,short bLen)
  148. {
  149.     /* remove leading space */
  150.     
  151.     aPtr++;
  152.     bPtr++;
  153.     aLen--;
  154.     bLen--;
  155.     
  156.     /* remove article numbers */
  157.  
  158.     while (aPtr && *aPtr != ' ') {
  159.         aPtr++;
  160.         aLen--;
  161.     }
  162.     while (bPtr && *bPtr != ' ') {
  163.         bPtr++;
  164.         bLen--;
  165.     }
  166.  
  167.     /* remove reply indicator */
  168.  
  169.     if (strncmp(aPtr," Re:",4)==0) {
  170.         aPtr += 4;
  171.         aLen -= 4;
  172.     }
  173.     if (strncmp(bPtr," Re:",4)==0) {
  174.         bPtr += 4;
  175.         bLen -= 4;
  176.     }
  177.     
  178.     return IUMagIDString(aPtr,bPtr,aLen,bLen);
  179. }
  180.  
  181.  
  182. /*    This routine removes colons, check marks, or returns from the titles of
  183.     articles.  This insures that the string returned is a legal filename.
  184. */
  185.  
  186. char *TitleFilter(char *title)
  187. {
  188.     short i;
  189.     
  190.     for (i=1; i<=title[0]; i++) {
  191.         switch (title[i]) {
  192.             case CR:
  193.             case ':':
  194.             case '├':
  195.                 title[i] = ' ';
  196.                 break;
  197.         }
  198.     }
  199.     if (title[0] > 31)
  200.         title[0] = 31;
  201.     return title;
  202. }
  203.  
  204.  
  205. /*    DoSaveWindow is called when the user issues a Save command for a
  206.     NEW article window.  The text of the article is saved to a text file
  207.     on disk.
  208. */
  209.  
  210. Boolean DoSaveWindow(WindowPtr wind)
  211. {
  212.     Point where = {100,100};
  213.     SFReply reply;
  214.     Str255 defaultName;
  215.     TwindowInfo *info;
  216.     OSErr err;
  217.     long count;
  218.     short fRefNum;
  219.     
  220.     GetWTitle(wind,defaultName);
  221.     TitleFilter((char *)defaultName);
  222.     
  223.     info = (TwindowInfo *)GetWRefCon(wind);
  224.     
  225.     SFPutFile(where,(StringPtr)"\pSave message as:",defaultName,nil,&reply);
  226.     if (!reply.good)
  227.         return false;
  228.         
  229.     if ((err = Create(reply.fName,reply.vRefNum,kTextCreator,'TEXT')) == dupFNErr) {
  230.         FSOpen(reply.fName,reply.vRefNum,&fRefNum);
  231.         err = SetEOF(fRefNum,0);
  232.         FSClose(fRefNum);
  233.     }
  234.     if (err != noErr)
  235.         return false;
  236.         
  237.     if (FSOpen(reply.fName,reply.vRefNum,&fRefNum) != noErr)
  238.         return false;
  239.     
  240.     HLock((**(TEHandle)info->data).hText);
  241.     count = (**(TEHandle)info->data).teLength;
  242.     FSWrite(fRefNum,&count,*(**(TEHandle)info->data).hText);
  243.     FSClose(fRefNum);
  244.     HUnlock((**(TEHandle)info->data).hText);
  245.     return true;
  246. }
  247.  
  248.  
  249. /*    DoSaveMessage is called when the user wishes to save the text
  250.     of an article which was fetched from the network.  In this case,
  251.     the full text of the article must be fetched before the save, since
  252.     we must allow >32k messages.
  253. */
  254.  
  255. Boolean DoSaveMessage(TwindowInfo *info)
  256. {
  257.     Point where = {100,100};
  258.     SFReply reply;
  259.     Str255 defaultName;
  260.     char *text;
  261.     long length;
  262.     long offset,offset2,offset3;
  263.     Handle teText;
  264.     char messageID[256];
  265.     OSErr err;
  266.     short fRefNum;
  267.     char mungeText[256];
  268.     
  269.     if (!FrontWindow()) {
  270.         SysBeep(1);
  271.         return false;
  272.     }
  273.     
  274.     GetWTitle(FrontWindow(),defaultName);
  275.     
  276.     teText = (Handle) TEGetText((TEHandle)info->data);
  277.  
  278.     offset = Munger(teText,0L,"Message-ID:",11L,nil,0L);
  279.     offset2 = Munger(teText,offset,CRSTR,1L,nil,0L);
  280.     strcpy(mungeText,CRSTR);
  281.     strcat(mungeText,CRSTR);
  282.     offset3 = Munger(teText,0L,mungeText,2L,nil,0L);
  283.     
  284.     if (offset < 0 || offset2 < 0 || offset3 < 0 || offset2 > offset3)
  285.         return false;
  286.     offset += 11;
  287.     
  288.     HLock(teText);
  289.     strncpy(messageID,(*teText)+offset,offset2 - offset);
  290.     messageID[offset2-offset] = '\0';
  291.     HUnlock(teText);
  292.     
  293.     if (GetArticle(nil,messageID,&text,&length,kMaxSaveLength) != noErr)
  294.         return false;
  295.         
  296.     TitleFilter((char *)defaultName);
  297.     SFPutFile(where,(StringPtr)"\pSave message as:",defaultName,nil,&reply);
  298.     if (!reply.good)
  299.         return false;
  300.  
  301.     if ((err = Create(reply.fName,reply.vRefNum,kTextCreator,'TEXT')) == dupFNErr) {
  302.         FSOpen(reply.fName,reply.vRefNum,&fRefNum);
  303.         err = SetEOF(fRefNum,0);
  304.         FSClose(fRefNum);
  305.     }
  306.  
  307.     if (err != noErr) {
  308.         MyDisposPtr(text);
  309.         return false;
  310.     }
  311.         
  312.     if (FSOpen(reply.fName,reply.vRefNum,&fRefNum) != noErr) {
  313.         MyDisposPtr(text);
  314.         return false;
  315.     }
  316.     
  317.     FSWrite(fRefNum,&length,text);
  318.         
  319.     FSClose(fRefNum);
  320.     MyDisposPtr(text);
  321. }
  322.  
  323.  
  324. /*    DoOpenFile is called when the user issues an Open Group List command
  325.     from the File menu.  This will prompt for a groups list to open.
  326. */
  327.  
  328. void DoOpenFile(void)
  329. {
  330.     Point where = {100,100};
  331.     SFReply reply;
  332.     
  333.     SFGetFile(where,(StringPtr)"\p",nil,1,(OSType *)"NEWS",nil,&reply);
  334.     if (reply.good)
  335.         LoOpenFile(reply.fName,reply.vRefNum);
  336. }
  337.  
  338.  
  339. /*    LoOpenFile is called by DoOpenFile once a name and vRefNum have been
  340.     entered by the user.  This routine then opens the file and parses it
  341.     for groups.
  342. */
  343.  
  344. OSErr LoOpenFile(Str255 fName,short vRefNum)
  345. {
  346.     OSErr err;
  347.     short fRefNum;
  348.     long length;
  349.     char *newsrc,*current;
  350.     WindowPtr window;
  351.     TwindowInfo *info;
  352.     Point thePt;
  353.     extern TPrefRec gPrefs;
  354.     
  355.     if ((err = FSOpen(fName,vRefNum,&fRefNum)) != noErr)
  356.         return err;
  357.     if ((err = GetEOF(fRefNum,&length)) == noErr) {    
  358.         newsrc = current = (char *)MyNewPtr(length+1);
  359.         if ((err = MyMemErr()) == noErr) {
  360.             if ((err = FSRead(fRefNum,&length,newsrc)) == noErr) {
  361.                 newsrc[length] = '\0';
  362.                 info = (TwindowInfo *)GetWRefCon(window = NewGroupWindow(fName));
  363.                 BlockMove(fName,info->diskFile,fName[0]+1);
  364.                 info->diskVRefNum = vRefNum;
  365.                 LDoDraw(false,(ListHandle)info->data);
  366.                 while (*current)
  367.                     ProcessLine(¤t,window);
  368.                 LDoDraw(true,(ListHandle)info->data);
  369.                 SetPt(&thePt,0,0);
  370.                 LSetSelect(true,thePt,(ListHandle)info->data);
  371.                 info->changed = false;
  372.                 info->saved = true;
  373.                 if (gPrefs.openWindowsZoomed)
  374.                     ToggleZoom(window);
  375.                 ShowWindow(window);
  376.                 SetPort(window);
  377.                 InvalRect(&window->portRect);
  378.             }
  379.             MyDisposPtr(newsrc);
  380.         }
  381.     }
  382.     FSClose(fRefNum);
  383.     SetCursor(&QDARROW);
  384.     return err;
  385. }
  386.  
  387.  
  388. /*    ProcessLine parses out a group name and group information for a single
  389.     line in the newsrc file.  The group is then added to the new window.
  390. */
  391.  
  392. void ProcessLine(char **newsrc,WindowPtr window)
  393. {
  394.     char newsGroup[256],*current;
  395.     extern short gNumGroups;
  396.     extern TGroup *gGroupList;
  397.     TGroup *newGroup;
  398.     long i,prev,totalLast,first,last;
  399.     Boolean done;
  400.     Cell theCell;
  401.     short nameLen;
  402.     
  403.     for (current = newsGroup;
  404.         **newsrc && **newsrc != ':' && **newsrc != '!';
  405.         *current++ = *(*newsrc)++)
  406.         ;
  407.     *current = '\0';
  408.     if (**newsrc == ':') {
  409.         (*newsrc)++;
  410.         for (i=0; i<gNumGroups && strcmp(newsGroup,gGroupList[i].name)!=0; i++)
  411.             ;
  412.         if (i<gNumGroups && (newGroup = Subscribe(&gGroupList[i],window,&theCell))) {
  413.             prev = newGroup->read->firstRead;
  414.             totalLast = newGroup->read->lastRead;
  415.             MyDisposPtr((Ptr)newGroup->read);
  416.             newGroup->read = nil;
  417.             
  418.             done = false;
  419.             do {
  420.                 while (**newsrc && **newsrc != CR && **newsrc<'0' && **newsrc>'9')
  421.                     (*newsrc)++;
  422.                 if (**newsrc && **newsrc != CR) {
  423.                     GetBlank(newsrc);
  424.                     GetNumber(newsrc,&first);
  425.                     GetBlank(newsrc);
  426.                     if (**newsrc=='-') {
  427.                         (*newsrc)++;
  428.                         GetBlank(newsrc);
  429.                         GetNumber(newsrc,&last);
  430.                     }
  431.                     else
  432.                         last = first;
  433.                     if (prev <= (first-1))
  434.                         MarkRead(prev,first-1,newGroup);
  435.                     prev = last+1;
  436.                 }
  437.                 else {
  438.                     done = true;
  439.                     if (totalLast >= last+1)
  440.                         MarkRead(last+1,totalLast,newGroup);
  441.                 }
  442.             } while (!done);
  443.             
  444.             if (newGroup->read==nil) {
  445.                 nameLen = 256;
  446.                 LGetCell(newsGroup,&nameLen,theCell,(ListHandle)((TwindowInfo *)GetWRefCon(window))->data);
  447.                 if ( ((unsigned char) newsGroup[nameLen-1]) != 0xff ) {
  448.                     newsGroup[strlen(newsGroup)+1] = 0xff;
  449.                     LSetCell(newsGroup,strlen(newsGroup)+2,theCell,(ListHandle)((TwindowInfo *)GetWRefCon(window))->data);
  450.                 }
  451.             }
  452.  
  453.         }
  454.         GiveTime(0);
  455.     }
  456.     while (**newsrc && **newsrc != CR)
  457.         (*newsrc)++;
  458.     if (**newsrc)
  459.         (*newsrc)++;
  460. }
  461.  
  462.  
  463. /*    GetNumber parses a number out of a string, returing the number and moving the
  464.     string pointer past the position of the number.
  465. */
  466.  
  467. void GetNumber(char **newsrc,long *number)
  468. {
  469.     char *current,numStr[256];
  470.     
  471.     current = numStr;
  472.     while (**newsrc && **newsrc>='0' && **newsrc<='9')
  473.         *current++ = *(*newsrc)++;
  474.     *current = '\0';
  475.     
  476.     StringToNum((StringPtr)c2pstr(numStr),number);
  477. }
  478.  
  479.  
  480. /*    GetBlank eats blank characters from an input string.
  481. */
  482.  
  483. void GetBlank(char **newsrc)
  484. {
  485.     while (**newsrc && **newsrc == ' ' || **newsrc == ',')
  486.         (*newsrc)++;
  487. }
  488.  
  489.  
  490. /*    CheckForSave asks the user if he/she would like to save a group
  491.     list before the changes have been discarded.
  492. */
  493.  
  494. Boolean CheckForSave(WindowPtr wind)
  495. {
  496.     TwindowInfo *info;
  497.     
  498.     info = (TwindowInfo *)GetWRefCon(wind);
  499.     ParamText(info->diskFile,"\p","\p","\p");
  500.     SetCursor(&QDARROW);
  501.     switch (Alert(kCheckSaveID,CmdKeyFilter)) {
  502.         case kYes:
  503.             return (DoSaveFile(wind));
  504.             break;
  505.         case kNo:
  506.             return true;
  507.             break;
  508.         case kCancel:
  509.             return false;
  510.             break;
  511.     }
  512. }
  513.  
  514.  
  515. /*    CheckForSend asks the user if he/she would like to send the message
  516.     that they composed before the window has been closed.
  517. */
  518.  
  519. Boolean CheckForSend(WindowPtr wind)
  520. {
  521.     SetCursor(&QDARROW);
  522.     switch (Alert(kAskSendAlert,CmdKeyFilter)) {
  523.         case kYes:
  524.             return (DoSendMsg((TwindowInfo *)GetWRefCon(wind)));
  525.             break;
  526.         case kNo:
  527.             return true;
  528.         case kCancel:
  529.             return false;
  530.     }
  531. }
  532.  
  533.  
  534. /*    DoSaveFile is called to save a group list to disk in newsrc format.
  535. */
  536.  
  537. Boolean DoSaveFile(WindowPtr wind)
  538. {
  539.     TwindowInfo *info;
  540.     
  541.     info = (TwindowInfo *)GetWRefCon(wind);
  542.     if (info->saved) {
  543.         info->changed = false;
  544.         return LoSaveFile(info->childList,(TGroup *)info->data2,info->diskFile,info->diskVRefNum);
  545.     }
  546.     else
  547.         return DoSaveAsFile(wind);
  548. }
  549.  
  550.  
  551. /*    DoSaveAsFile is called when the group list to be saved has not been
  552.     written to disk before.
  553. */
  554.  
  555. Boolean DoSaveAsFile(WindowPtr wind)
  556. {
  557.     Point where = {100,100};
  558.     SFReply reply;
  559.     TwindowInfo *info;
  560.     Boolean good;
  561.     
  562.     info = (TwindowInfo *)GetWRefCon(wind);
  563.     
  564.     SFPutFile(where,"\pSave group list as:",info->diskFile,nil,&reply);
  565.     if (!reply.good)
  566.         return false;
  567.         
  568.     if (good = LoSaveFile(info->childList,(TGroup *)info->data2,reply.fName,reply.vRefNum)) {
  569.         RemoveWindowsMenu(wind);
  570.         SetWTitle(wind,reply.fName);
  571.         AddWindowsMenu(wind);
  572.         BlockMove(reply.fName,info->diskFile,reply.fName[0]+1);
  573.         info->diskVRefNum = reply.vRefNum;
  574.         info->changed = false;
  575.         info->saved = true;
  576.     }
  577.     return good;
  578. }
  579.  
  580.  
  581. /*    LoSaveFile is the low-level routine which writes the groups out to the
  582.     group file on disk.
  583. */
  584.  
  585. Boolean LoSaveFile(TWList *children,TGroup *groups,Str255 fName,short vRefNum)
  586. {
  587.     OSErr err;
  588.     short fRefNum;
  589.     long length,first;
  590.     TGroup *theGroup;
  591.     char tmpStr[256];
  592.     TReadRec *read;
  593.     TWList *current;
  594.     short offset;
  595.     
  596.     /* update read lists */
  597.     
  598.     for (current = children; current != nil; current = current->next)
  599.         MarkReadMsgs((TwindowInfo *) GetWRefCon(current->childWindow));
  600.  
  601.     if ((err = Create(fName,vRefNum,kFCreator,kFType)) == dupFNErr) {
  602.         FSOpen(fName,vRefNum,&fRefNum);
  603.         err = SetEOF(fRefNum,0);
  604.         FSClose(fRefNum);
  605.     }
  606.     if (err != noErr)
  607.         return false;
  608.         
  609.     if (FSOpen(fName,vRefNum,&fRefNum) != noErr)
  610.         return false;
  611.     
  612.     for (theGroup = groups; theGroup!=nil; theGroup=theGroup->next) {
  613.         length = strlen(theGroup->name);
  614.         FSWrite(fRefNum,&length,theGroup->name);
  615.         length = 2;
  616.         FSWrite(fRefNum,&length,": ");
  617.         
  618.         first = 1;
  619.         for (read = theGroup->read; read!=nil; read = read->next) {
  620.             if ( (read->firstRead-1 - first) >= 0) {
  621.                 sprintf(tmpStr,"%lu-%lu",first,(read->firstRead-1));
  622.                 if (read->next)
  623.                     strcat(tmpStr,",");
  624.                 length = strlen(tmpStr);
  625.                 FSWrite(fRefNum,&length,tmpStr);
  626.             }
  627.             first = 1 + read->lastRead;
  628.         }
  629.         
  630.         /* mark read to last message of group */
  631.         
  632.         if (theGroup->lastMess >= first) {
  633.                 sprintf(tmpStr,", %lu-%lu",first,theGroup->lastMess);
  634.                 if (first==1)
  635.                     offset=1;
  636.                 else
  637.                     offset=0;
  638.                 length = strlen(tmpStr+offset);
  639.                 FSWrite(fRefNum,&length,tmpStr+offset);
  640.         }
  641.         
  642.         length = 1;
  643.         FSWrite(fRefNum,&length,CRSTR);
  644.     }
  645.     FSClose(fRefNum);
  646.     return true;
  647. }
  648.  
  649.  
  650. /*    MarkReadMsgs is called to update the read messages list for a group.
  651.     Each subject in the list preceeded by a ├ (check mark) is marked as
  652.     read in the internal data structure.
  653. */
  654.  
  655. void MarkReadMsgs(TwindowInfo *theInfo)
  656. {
  657.     long tmpFirst,tmpLast,i;
  658.     TReadRec *read,*tmp;
  659.     TGroup *theGroup;
  660.     TSubject *subjects;
  661.     TwindowInfo *parentInfo;
  662.     char groupName[256];
  663.     short nameLen;
  664.     Cell theCell;
  665.     
  666.     theGroup = theInfo->parentGroup;
  667.     parentInfo = (TwindowInfo *)GetWRefCon(theInfo->parentWindow);
  668.     parentInfo->changed = true;
  669.     
  670.     subjects = (TSubject *)*(Handle)theInfo->data2;
  671.     
  672.     read = theGroup->read;
  673.     while (read != nil) {
  674.         tmp = read;
  675.         read = read->next;
  676.         MyDisposPtr((Ptr)tmp);
  677.     }
  678.     theGroup->read = nil;
  679.     
  680.     tmpFirst = -1;
  681.         
  682.     for (i=0; i<theInfo->numSubjects; i++) {            
  683.         if (!subjects[i].read && i<(theInfo->numSubjects-1) && subjects[i+1].number-subjects[i].number > 1) {
  684.             if (tmpFirst == -1)
  685.                 tmpFirst = subjects[i].number;
  686.             MarkRead(tmpFirst,subjects[i].number,theGroup);
  687.             tmpFirst = -1;
  688.         }
  689.         else if (tmpFirst == -1 && !subjects[i].read)
  690.             tmpFirst = tmpLast = subjects[i].number;
  691.         else if (tmpFirst != -1 && !subjects[i].read)
  692.             tmpLast = subjects[i].number;
  693.         else if (tmpFirst != -1 && subjects[i].read) {
  694.             MarkRead(tmpFirst,tmpLast,theGroup);
  695.             tmpFirst = -1;
  696.         }
  697.     }
  698.     if (tmpFirst != -1)
  699.         MarkRead(tmpFirst,tmpLast,theGroup);
  700.  
  701.     if (theGroup->read==nil) {
  702.         SetPt(&theCell,0,0);
  703.         if (LSearch(theGroup->name,strlen(theGroup->name),CompareStart,&theCell,(ListHandle)parentInfo->data)) {
  704.             nameLen = 256;
  705.             LGetCell(groupName,&nameLen,theCell,(ListHandle)parentInfo->data);
  706.             if ( ((unsigned char) groupName[nameLen-1]) != 0xff ) {
  707.                 groupName[strlen(groupName)+1] = 0xff;
  708.                 LSetCell(groupName,strlen(groupName)+2,theCell,(ListHandle)parentInfo->data);
  709.             }
  710.         }
  711.     }
  712. }
  713.  
  714.  
  715. /*    CompareStart is a string comparison routine which returns the strings as
  716.     equal if they share a common prefix.  This routine also ignores leading spaces
  717.     and check marks.
  718. */
  719.  
  720. pascal short CompareStart(Ptr aPtr,Ptr bPtr,short aLen,short bLen)
  721. {
  722.     while (*aPtr == ' ' || *aPtr == '├') {
  723.         aPtr++;
  724.         aLen--;
  725.     }
  726.     while (*bPtr == ' ' || *bPtr == '├') {
  727.         bPtr++;
  728.         bLen--;
  729.     }
  730.     
  731.     if (aLen < bLen)
  732.         bLen = aLen;
  733.     else if (bLen < aLen);
  734.         aLen = bLen;
  735.     
  736.     return IUMagIDString(aPtr,bPtr,aLen,bLen);
  737. }
  738.  
  739.  
  740. /*    MarkRead marks a range of messages _unread_ in the internal database for
  741.     the specified group.
  742. */
  743.  
  744. void MarkRead(long first,long last,TGroup *theGroup)
  745. {
  746.     TReadRec *read,*lastRec;
  747.     
  748.     read = (TReadRec *)MyNewPtr(sizeof(TReadRec));
  749.     if (MyMemErr() != noErr)
  750.         return;
  751.         
  752.     read->firstRead = first;
  753.     read->lastRead = last;
  754.     read->next = nil;
  755.     if (theGroup->read) {
  756.         for (lastRec = theGroup->read; lastRec->next; lastRec=lastRec->next)
  757.             ;
  758.         lastRec->next = read;
  759.     }
  760.     else theGroup->read = read;
  761. }
  762.  
  763.  
  764. /*    MarkXrefsRead marks articles which appear in multiple newsgroups as read
  765.     in both of those newsgroups.
  766. */
  767.  
  768. void MarkXrefsRead(TEHandle message,TGroup *groupList)
  769. {
  770.     long offset,endHeader,endLine;
  771.     Handle theText;
  772.     char *current,*store;
  773.     char xrefGroup[256];
  774.     char xrefNumber[256];
  775.     long number;
  776.     char mungeText[256];
  777.     
  778.     strcpy(mungeText,CRSTR);
  779.     strcat(mungeText,"Xref:");
  780.     
  781.     theText = (Handle) TEGetText((TEHandle)message);
  782.     offset = Munger((Handle)theText,0L,mungeText,6L,nil,0L);
  783.     if (offset < 0) {
  784.         offset = Munger(theText,0L,"Xref:",5L,nil,0L);
  785.         if (offset != 0)
  786.             offset = -1;
  787.     }
  788.     if (offset < 0)
  789.         return;
  790.     
  791.     strcpy(mungeText,CRSTR);
  792.     strcat(mungeText,CRSTR);
  793.     endHeader = Munger(theText,0L,mungeText,2L,nil,0L);
  794.     if (offset > endHeader)
  795.         return;
  796.     
  797.     endLine = Munger(theText,offset,CRSTR,1L,nil,0L);
  798.     
  799.     HLock(theText);
  800.     current = (*theText)+offset;
  801.     
  802.     /* skip over site name */
  803.     
  804.     current += 6;
  805.     while (*current == ' ' && *current != CR)
  806.         current++;
  807.     while (*current != ' ' && *current != CR)
  808.         current++;
  809.     
  810.     /* parse xrefed groups */
  811.     
  812.     while (current < (*theText)+endLine) {
  813.         store = xrefGroup;
  814.         while (*current == ' ' && *current != CR)
  815.             current++;
  816.         while (*current != ':' && *current != ' ' && *current != CR) {
  817.             *store++ = *current++;
  818.         }
  819.         current++;
  820.         *store = '\0';
  821.         
  822.         store = xrefNumber;
  823.         while (*current != ' ' && *current != ' ' && *current != CR) 
  824.             *store++ = *current++;
  825.         *store = '\0';
  826.         c2pstr(xrefNumber);
  827.         StringToNum(xrefNumber,&number);
  828.         MarkOneRead(xrefGroup,number,groupList);
  829.     }
  830.     
  831.     HUnlock(theText);
  832. }
  833.  
  834.  
  835. /*    MarkOneRead marks a single message in a specified group as being read.
  836. */
  837.  
  838. void MarkOneRead(char *groupName,long number,TGroup *groupList)
  839. {
  840.     TGroup *theGroup;
  841.     TReadRec *read,*newRead,*prevRead;
  842.     
  843.     for (theGroup = groupList;
  844.         theGroup && strcmp(theGroup->name,groupName)!=0;
  845.         theGroup = theGroup->next);
  846.     
  847.     if (!theGroup)
  848.         return;
  849.         
  850.     for (read = prevRead = theGroup->read;
  851.         read && !(number>=read->firstRead  && number<=read->lastRead);
  852.         prevRead = read,read = read->next);
  853.     
  854.     if (!read)    /* already read */
  855.         return;
  856.     
  857.     if (number == read->firstRead) {
  858.         read->firstRead++;
  859.         if (read->firstRead == read->lastRead) {
  860.             if (read != prevRead)
  861.                 prevRead->next = read->next;
  862.             else
  863.                 theGroup->read = read->next;
  864.             DisposPtr((Ptr)read);
  865.         }
  866.         return;
  867.     }
  868.  
  869.     if (number == read->lastRead) {
  870.         read->lastRead--;
  871.         if (read->firstRead == read->lastRead) {
  872.             if (read != prevRead)
  873.                 prevRead->next = read->next;
  874.             else
  875.                 theGroup->read = read->next;
  876.             DisposPtr((Ptr)read);
  877.         }
  878.         return;
  879.     }
  880.     
  881.     /* split this one in half */
  882.     
  883.     newRead = (TReadRec *)NewPtr(sizeof(TReadRec));
  884.     newRead->next = read->next;
  885.     read->next = newRead;
  886.     newRead->lastRead = read->lastRead;
  887.     newRead->firstRead = number + 1;
  888.     read->lastRead = number - 1;
  889. }
  890.  
  891.  
  892. /*    Subscribe adds the given group to the list of groups to which the
  893.     user is subscribed.  This is called when a user drags a group from
  894.     the main group list into his/her user group list.
  895. */
  896.  
  897. TGroup *Subscribe(TGroup *theGroup,WindowPtr theWindow,Cell *retCell)
  898. {
  899.     ListHandle theList;
  900.     Cell theCell;
  901.     TwindowInfo *theInfo;
  902.     TGroup *tmpPtr,*last;
  903.     short cellSize;
  904.     char tmpName[256];
  905.     
  906.     theInfo = (TwindowInfo *)GetWRefCon(theWindow);
  907.     theInfo->changed = true;
  908.     
  909.     for (tmpPtr = (TGroup *)theInfo->data2;
  910.         tmpPtr && strcmp(theGroup->name,tmpPtr->name)!=0;
  911.         tmpPtr = tmpPtr->next)
  912.             ;
  913.             
  914.     if (tmpPtr) {
  915.         SysBeep(1);
  916.         return nil;
  917.     }
  918.  
  919.     /* I do this to keep order by inserting at the end of newsgroups */
  920.             
  921.     tmpPtr = (TGroup *)MyNewPtr(sizeof(TGroup));
  922.     if (MyMemErr() != noErr)
  923.         return nil;
  924.                 
  925.     strcpy(tmpPtr->name,theGroup->name);
  926.     tmpPtr->firstMess = theGroup->firstMess;
  927.     tmpPtr->lastMess = theGroup->lastMess;
  928.     tmpPtr->status = theGroup->status;
  929.     
  930.     tmpPtr->read = (TReadRec *)MyNewPtr(sizeof(TReadRec));
  931.     if (MyMemErr() != noErr)
  932.         return nil;
  933.         
  934.     tmpPtr->read->firstRead = theGroup->firstMess;
  935.     tmpPtr->read->lastRead = theGroup->lastMess;
  936.     tmpPtr->read->next = nil;
  937.     
  938.     strcpy(tmpName,tmpPtr->name);
  939.     if ( (tmpPtr->lastMess - tmpPtr->firstMess) <= 0 ) {
  940.         cellSize = strlen(tmpName)+2;
  941.         tmpName[strlen(tmpName)+1] = 0xff;
  942.     }
  943.     else
  944.         cellSize = strlen(tmpName)+1;
  945.     
  946.     tmpPtr->next = nil;
  947.  
  948.     for (last = (TGroup *) theInfo->data2;
  949.         last && last->next != nil;
  950.         last = last->next)
  951.         ;
  952.     if (!last)
  953.         theInfo->data2 = (unsigned long) tmpPtr;
  954.     else
  955.         last->next = tmpPtr;
  956.     
  957.     theList = (ListHandle)theInfo->data;
  958.     SetPt(&theCell,0,LAddRow(1,theInfo->numGroups++,theList));
  959.     LSetCell(tmpName,cellSize,theCell,theList);
  960.     LDraw(theCell,theList);
  961.  
  962.     *retCell = theCell;
  963.     return tmpPtr;
  964. }
  965.  
  966.  
  967. /*    DoMarkArticleRead is called when a user selects Mark Read from the
  968.     News menu.  It marks all selected articles in window read.
  969. */
  970.  
  971. void DoMarkArticleRead(Boolean read)
  972. {
  973.     TwindowInfo *info;
  974.     Cell theCell;
  975.     
  976.     if (!IsAppWindow(FrontWindow()))
  977.         return;
  978.     info = (TwindowInfo *)GetWRefCon(FrontWindow());
  979.     if (info->kind != cSubject)
  980.         return;
  981.     
  982.     theCell.h = theCell.v = 0;
  983.     while (LGetSelect(true,&theCell,(ListHandle)info->data)) {
  984.         LoMarkArticleRead(info,theCell,read);
  985.         theCell.v++;
  986.     }
  987. }
  988.  
  989.  
  990. /*    LoMarkArticleRead is the low-level procedure which inserts check marks
  991.     into the subjects which are passed in at theCell.
  992. */
  993.  
  994. void LoMarkArticleRead(TwindowInfo *info,Cell theCell,Boolean read)
  995. {
  996.     short nameLen;
  997.     char *tmpStr,*tmpStr2;
  998.     char numStr[256],title[256];
  999.     long number,i;
  1000.     
  1001.     nameLen = 256;
  1002.     LGetCell(title,&nameLen,theCell,(ListHandle)info->data);
  1003.  
  1004.     for (tmpStr = numStr,tmpStr2 = title+1;
  1005.         *tmpStr2 != ' ' && *tmpStr2 != '├' && *tmpStr2 != '\0';
  1006.         *tmpStr++ = *tmpStr2++)
  1007.         ;
  1008.     *tmpStr = '\0';
  1009.     c2pstr(numStr);
  1010.     StringToNum(numStr,&number);
  1011.  
  1012.     if (info->parentGroup) {
  1013.         *title = (read) ? '├' : ' ';
  1014.         LSetCell(title,nameLen,theCell,(ListHandle)info->data);
  1015.         for (i=0; i<info->numSubjects && ((TSubject *)*((Handle)info->data2))[i].number != number; i++)
  1016.             ;
  1017.         if (i<=info->numSubjects)
  1018.             ((TSubject *)*((Handle)info->data2))[i].read = read;
  1019.         else
  1020.             SysBeep(1);
  1021.     }
  1022. }
  1023.  
  1024.  
  1025. /*    DoMarkGroupRead marks all selected groups in a group window read.
  1026.     It is also called in response to a Mark Read command in the News
  1027.     menu.
  1028. */
  1029.  
  1030. void DoMarkGroupRead(Boolean read)
  1031. {
  1032.     TwindowInfo *info;
  1033.     Cell theCell;
  1034.     short nameLen;
  1035.     TWList *children;
  1036.     Str255 wTitle;
  1037.     char name[256];
  1038.     Boolean matched;
  1039.     unsigned char readChar;
  1040.     
  1041.     readChar = (read) ? 0xff : 0x00;
  1042.     
  1043.     if (!IsAppWindow(FrontWindow()))
  1044.         return;
  1045.     info = (TwindowInfo *)GetWRefCon(FrontWindow());
  1046.     if (info->kind != cUserGroup)
  1047.         return;
  1048.     
  1049.         
  1050.     theCell.h = theCell.v = 0;
  1051.     while (LGetSelect(true,&theCell,(ListHandle)info->data)) {
  1052.         nameLen = 256;
  1053.         LGetCell(name,&nameLen,theCell,(ListHandle)info->data);
  1054.         for (matched=false,children=info->childList; children!=nil && !matched; children=children->next) {
  1055.             GetWTitle(children->childWindow,wTitle);
  1056.             p2cstr(wTitle);
  1057.             matched = (strcmp((char *)wTitle,name) == 0);
  1058.         }
  1059.         if (!matched) {
  1060.             if ( ((unsigned char) name[nameLen-1]) != readChar) {
  1061.                 name[strlen(name)+1] = readChar;
  1062.                 LSetCell(name,strlen(name)+2,theCell,(ListHandle)info->data);
  1063.             }
  1064.             LoMarkGroupRead(name,(TGroup *)info->data2,read);
  1065.         }
  1066.         else
  1067.             SysBeep(1);
  1068.             
  1069.         theCell.v++;
  1070.     }
  1071. }
  1072.  
  1073.  
  1074. /*    LoMarkGroupRead marks all articles in the selected group read.
  1075. */
  1076.  
  1077. void LoMarkGroupRead(char groupName[256],TGroup *firstGroup,Boolean isRead)
  1078. {
  1079.     TGroup *group;
  1080.     TReadRec *read;
  1081.     
  1082.     for (group = firstGroup;
  1083.                 group != nil && strcmp(group->name,groupName)!=0;
  1084.                 group = group->next)
  1085.         ;
  1086.     
  1087.     while (group->read) {
  1088.         read = group->read;
  1089.         group->read = group->read->next;
  1090.         MyDisposPtr((Ptr)read);
  1091.     }
  1092.     
  1093.     if (!isRead) {
  1094.         read = (TReadRec *) MyNewPtr(sizeof(TReadRec));
  1095.         read->firstRead = group->firstMess;
  1096.         read->lastRead = group->lastMess;
  1097.         read->next = group->read;
  1098.         group->read = read;
  1099.     }
  1100. }
  1101.  
  1102.  
  1103. /*    SubscribeSelected subscribes the user to all selected newsgroups
  1104.     by calling Subscribe() above.
  1105. */
  1106.  
  1107. void SubscribeSelected(TwindowInfo *info,ListHandle srcList,WindowPtr destWindow)
  1108. {
  1109.     Cell theCell,newCell;
  1110.     TGroup *theGroup;
  1111.     char groupName[256];
  1112.     short nameLen;
  1113.     
  1114.     SetPt(&theCell,0,0);
  1115.     
  1116.     while (LGetSelect(true,&theCell,srcList)) {
  1117.         nameLen = 256;
  1118.         LGetCell(groupName,&nameLen,theCell,srcList);
  1119.         if (FindGroup(info,groupName,&theGroup))
  1120.             Subscribe(theGroup,destWindow,&newCell);
  1121.         theCell.v++;
  1122.     }
  1123. }
  1124.  
  1125.  
  1126. /*    HandleSubscribe is called when the user selects the Subscribe command
  1127.     from the News menu.  It calls SubscribeSelected() above.
  1128. */
  1129.  
  1130. void HandleSubscribe(void)
  1131. {
  1132.     TwindowInfo *info;
  1133.     WindowPtr destWindow;
  1134.     Boolean done;
  1135.     
  1136.     if (!FrontWindow()) {
  1137.         SysBeep(1);
  1138.         return;
  1139.     }
  1140.     
  1141.     info = (TwindowInfo *)GetWRefCon(FrontWindow());
  1142.     if (info->kind != cGroup && info->kind != cNewGroup)
  1143.         return;
  1144.         
  1145.     for (done=false,destWindow = (WindowPtr) ((WindowPeek)FrontWindow())->nextWindow;
  1146.         destWindow && (((TwindowInfo *)GetWRefCon(destWindow))->kind != cUserGroup);
  1147.         destWindow = (WindowPtr) ((WindowPeek)destWindow)->nextWindow)
  1148.             ;
  1149.     if (!destWindow)
  1150.         return;
  1151.     
  1152.     SubscribeSelected(info,(ListHandle)(info->data),destWindow);
  1153. }
  1154.  
  1155.  
  1156. /*    HandleUnsubscribe unsubscribes the user from the selected newsgroups.
  1157.     It is called when the user selects unsubscribe from the News menu.
  1158. */
  1159.  
  1160. void HandleUnsubscribe(void)
  1161. {
  1162.     TwindowInfo *info;
  1163.     ListHandle theList;
  1164.     Cell theCell;
  1165.     Str255 groupName,wTitle;
  1166.     short groupLen;
  1167.     TGroup *curGroup,*prevGroup;
  1168.     TWList *curChild;
  1169.     Boolean doDelete;
  1170.     
  1171.     if (!FrontWindow()) {
  1172.         SysBeep(1);
  1173.         return;
  1174.     }
  1175.     
  1176.     info = (TwindowInfo *)GetWRefCon(FrontWindow());
  1177.     if (info->kind != cUserGroup)
  1178.         return;
  1179.     
  1180.     info->changed = true;
  1181.     
  1182.     theList = (ListHandle) info->data;
  1183.     SetPt(&theCell,0,0);
  1184.     
  1185.     while (LGetSelect(true,&theCell,theList)) {
  1186.         groupLen = 256;
  1187.         LGetCell(groupName,&groupLen,theCell,theList);
  1188.         doDelete = true;
  1189.         
  1190.         for (curChild = info->childList; curChild!=nil && doDelete; curChild = curChild->next) {
  1191.             GetWTitle(curChild->childWindow,wTitle);
  1192.             p2cstr(wTitle);
  1193.             if (strcmp(wTitle,groupName)==0)
  1194.                 doDelete = false;
  1195.         }
  1196.         
  1197.         if (doDelete) {
  1198.             LDelRow(1,theCell.v,theList);
  1199.             for (curGroup = prevGroup = (TGroup *)info->data2;
  1200.                 curGroup && strcmp(groupName,curGroup->name)!=0;
  1201.                 prevGroup = curGroup, curGroup = curGroup->next)
  1202.                 ;
  1203.             if (curGroup) {
  1204.                 if (curGroup==prevGroup)
  1205.                     info->data2 = (long)curGroup->next;
  1206.                 prevGroup->next = curGroup->next;
  1207.                 MyDisposPtr((Ptr)curGroup);
  1208.             }
  1209.         }
  1210.         else theCell.v++;
  1211.     }
  1212. }
  1213.  
  1214.  
  1215. /*    CheckGroups compares the list of groups to those stored in the preferences
  1216.     file.  The groups which are new are then put into a new groups window.
  1217. */
  1218.  
  1219. void CheckGroups(void)
  1220. {
  1221.     extern TGroup *gGroupList;
  1222.     extern short gNumGroups;
  1223.     
  1224.     OSErr err;
  1225.     short fRefNum;
  1226.     char *groupBuf;
  1227.     long groupLen = kMaxGroupLen;
  1228.     char *current,*group;
  1229.     long groupIndex;
  1230.     short cmp;
  1231.     Handle groupHandle = nil;
  1232.     long numGroups = 0;
  1233.     
  1234.     if ((err = FSOpen(kPrefName,BlessedFolder(),&fRefNum)) != noErr)
  1235.         return;
  1236.     
  1237.     if ((err = SetFPos(fRefNum,fsFromStart,sizeof(TPrefRec))) == noErr) {
  1238.         groupBuf = (char *)MyNewPtr(groupLen);
  1239.         if (MyMemErr() != noErr) {
  1240.             FSClose(fRefNum);
  1241.             return;
  1242.         }
  1243.         err = FSRead(fRefNum,&groupLen,(Ptr)groupBuf);
  1244.         if (err != noErr && err != eofErr)
  1245.             groupLen = 0;
  1246.         groupBuf[groupLen] = '\0';
  1247.         current = groupBuf;
  1248.         groupIndex = 0;
  1249.         group = GetNextGroup(¤t);
  1250.         while (groupIndex < gNumGroups) {
  1251.             GiveTime(0);
  1252.             cmp = strcmp(group,gGroupList[groupIndex].name);
  1253.             if (*group=='\0') {
  1254.                 AddNewGroup(&gGroupList[groupIndex],&groupHandle,&numGroups);
  1255.                 groupIndex++;
  1256.             }
  1257.             else if (cmp==0) {
  1258.                 group = GetNextGroup(¤t);
  1259.                 groupIndex++;
  1260.             }
  1261.             else if (cmp>0) {
  1262.                 AddNewGroup(&gGroupList[groupIndex],&groupHandle,&numGroups);
  1263.                 groupIndex++;
  1264.             }
  1265.             else if (cmp<0)
  1266.                 group = GetNextGroup(¤t);
  1267.         }
  1268.         MyDisposPtr(groupBuf);
  1269.         MakeNewGroupWindow(groupHandle,numGroups,"\pNew Groups");
  1270.     }
  1271.     
  1272.     FSClose(fRefNum);
  1273. }
  1274.  
  1275.  
  1276. /*    GetNextGroup parses the next group out of the preferences file
  1277. */
  1278.  
  1279. char *GetNextGroup(char **current)
  1280. {
  1281.     char *groupPos;
  1282.     
  1283.     while (**current == CR || **current == LF)
  1284.         (*current)++;
  1285.     groupPos = *current;
  1286.     while (**current && **current!=CR)
  1287.         (*current)++;
  1288.     if (**current)
  1289.         *(*current)++ = '\0';
  1290.     return groupPos;
  1291. }
  1292.  
  1293.  
  1294. /*    AddNewGroup adds a new group (one which is not in the prefs file) to
  1295.     the New Groups window.
  1296. */
  1297.  
  1298. void AddNewGroup(TGroup *group,Handle *groupHandle,long *numGroups)
  1299. {
  1300.     TGroup *groupList;
  1301.     
  1302.     if (*groupHandle == nil) {
  1303.         *groupHandle = MyNewHandle(sizeof(TGroup));
  1304.         if (MyMemErr() != noErr)
  1305.             return;
  1306.         HLock(*groupHandle);
  1307. }
  1308.     else {
  1309.         HUnlock(*groupHandle);
  1310.         MySetHandleSize(*groupHandle,GetHandleSize(*groupHandle)+sizeof(TGroup));
  1311.         if (MyMemErr() != noErr)
  1312.             return;
  1313.         MoveHHi(*groupHandle);
  1314.         HLock(*groupHandle);
  1315.     }
  1316.     groupList = (TGroup *) **groupHandle;
  1317.     BlockMove((Ptr)group,(Ptr)&groupList[(*numGroups)++],sizeof(TGroup));
  1318. }
  1319.  
  1320.  
  1321. /*    MakeNewGroupWindow creates a New Groups window to store the names of the
  1322.     new newsgroups.
  1323. */
  1324.  
  1325. void MakeNewGroupWindow(Handle groupHandle,long numGroups,char *windowName)
  1326. {
  1327.     TwindowInfo *theInfo;
  1328.     WindowPtr theWind;
  1329.     GrafPtr savePort;
  1330.     Cell theCell;
  1331.     short i;
  1332.     Point firstOffset;
  1333.     char theName[256];
  1334.     short cellLen;
  1335.     Point thePt;
  1336.     extern TPrefRec gPrefs;
  1337.     
  1338.     if (numGroups==0)
  1339.         return;
  1340.     
  1341.     SetPt(&firstOffset,kOffLeft,kOffTop);
  1342.     
  1343.     CloseStatusWindow();
  1344.     
  1345.     theInfo = (TwindowInfo *) GetWRefCon(theWind = MakeNewWindow(cNewGroup,true,firstOffset,windowName));
  1346.     
  1347.     StatusWindow("Adding New Groups...",0);
  1348.     
  1349.     theInfo->numGroups = numGroups;
  1350.     theInfo->parentGroup = nil;
  1351.     theInfo->parentWindow = nil;
  1352.     theInfo->childList = nil;
  1353.     theInfo->data2 = (unsigned long) *groupHandle;
  1354.     
  1355.     LDoDraw(false,(ListHandle)theInfo->data);
  1356.     LAddRow(numGroups,0,(ListHandle)theInfo->data);
  1357.     SetPt(&theCell,0,0);
  1358.     for (i=0; i<numGroups; i++) {
  1359.     
  1360.         strcpy(theName,((TGroup *)*groupHandle)[i].name);
  1361.         if ((((TGroup *)*groupHandle)[i].lastMess - ((TGroup *)*groupHandle)[i].firstMess) <= 0) {
  1362.             cellLen = strlen(theName)+2;
  1363.             theName[cellLen-1] = 0xff;
  1364.         }
  1365.         else
  1366.             cellLen = strlen(theName)+1;
  1367.             
  1368.         LSetCell(theName,cellLen,theCell,(ListHandle)theInfo->data);
  1369.         theCell.v++;
  1370.         StatusWindow("Adding New Groups...",(short)(((float)(i+1)/(float)numGroups)*100));
  1371.         GiveTime(0);
  1372.     }
  1373.     StatusWindow("Adding New Groups...",100);
  1374.     GiveTime(0);
  1375.     CloseStatusWindow();
  1376.     
  1377.     LDoDraw(true,(ListHandle)theInfo->data);
  1378.     SetPt(&thePt,0,0);
  1379.     LSetSelect(true,thePt,(ListHandle)theInfo->data);
  1380.     
  1381.     if (gPrefs.openWindowsZoomed)
  1382.         ToggleZoom(theWind);
  1383.     ShowWindow(theWind);
  1384.     
  1385.     GetPort(&savePort);
  1386.     SetPort(theWind);
  1387.     InvalRect(&theWind->portRect);
  1388.     SetPort(savePort);
  1389.  
  1390. }
  1391.  
  1392.  
  1393. /*    WriteGroups writes the list of newsgroups out to the preferences file
  1394.     before quitting the program.
  1395. */
  1396.  
  1397. void WriteGroups(void)
  1398. {
  1399.     extern TGroup *gGroupList;
  1400.     extern short gNumGroups;
  1401.     short fRefNum;
  1402.     long i,count;
  1403.     OSErr err;
  1404.     
  1405.     StatusWindow("Writing group list...",0);
  1406.     if ((err = FSOpen(kPrefName,BlessedFolder(),&fRefNum)) != noErr)
  1407.         return;
  1408.     
  1409.     err = SetFPos(fRefNum,fsFromStart,sizeof(TPrefRec));
  1410.     if (err == noErr || err == eofErr) {
  1411.         for (i=0; i<gNumGroups; i++) {
  1412.             count = strlen(gGroupList[i].name);
  1413.             FSWrite(fRefNum,&count,gGroupList[i].name);
  1414.             count = 1;
  1415.             FSWrite(fRefNum,&count,CRSTR);
  1416.             GiveTime(0);
  1417.             StatusWindow("Writing group list...",(short)(((float)(i+1)/(float)gNumGroups)*100));
  1418.         }
  1419.         StatusWindow("Writing group list...",100);
  1420.         GiveTime(0);
  1421.     }
  1422.     FSClose(fRefNum);
  1423. }
  1424.  
  1425.  
  1426. /*    MakeFollowUp creates a new article template for a follow-up back to
  1427.     a newsgroup.
  1428. */
  1429.  
  1430. void MakeFollowUp(void)
  1431. {
  1432.     DialogPtr theDlg;
  1433.     short item;
  1434.     TwindowInfo *theInfo;
  1435.     WindowPtr oldWind,theWind;
  1436.     Point firstOffset;
  1437.     GrafPtr savePort;
  1438.     char subjectStr[256],groupStr[256],fromStr[256],distStr[256],refStr[256];
  1439.     short iType;
  1440.     Handle iHndl;
  1441.     Rect iRect;
  1442.     extern TPrefRec gPrefs;
  1443.     Handle theHndl;
  1444.     long offset,offset2;
  1445.  
  1446.     if (!FrontWindow()) {
  1447.         SysBeep(1);
  1448.         return;
  1449.     }
  1450.     
  1451.     SetCursor(&QDARROW);
  1452.     
  1453.     oldWind = FrontWindow();
  1454.     
  1455.     SetPt(&firstOffset,0,0);
  1456.     GetPort(&savePort);
  1457.     SetPort(oldWind);
  1458.     LocalToGlobal(&firstOffset);
  1459.     SetPort(savePort);
  1460.  
  1461.     if (CautionAlert(kPostAlert,nil) != okButton)
  1462.         return;
  1463.  
  1464.     SetCursor(&QDARROW);
  1465.         
  1466.     theDlg = GetNewDialog(kPostDlg,nil,(WindowPtr)-1);
  1467.     OutlineOK(theDlg);
  1468.  
  1469.     theInfo = (TwindowInfo *) GetWRefCon(oldWind);
  1470.     theHndl = (Handle) TEGetText((TEHandle)theInfo->data);
  1471.     HLock(theHndl);
  1472.  
  1473.     /* copy subject field */
  1474.     
  1475.     offset = Munger(theHndl,0L,"Subject: ",9L,nil,0L);
  1476.     offset2 = Munger(theHndl,offset,CRSTR,1L,nil,0L);
  1477.     if (offset2 > offset && (offset2-offset) < 255 ) {
  1478.         strcpy(subjectStr,"Re: ");
  1479.         if (strncmp( (*theHndl)+offset+9,"Re: ",4) == 0)
  1480.             offset += 4;
  1481.         BlockMove( (*theHndl)+offset+9,(Ptr) (subjectStr+4),(offset2-offset-9));
  1482.         subjectStr[offset2-offset-9+4] = '\0';
  1483.         c2pstr(subjectStr);
  1484.         GetDItem(theDlg,5,&iType,&iHndl,&iRect);
  1485.         SetIText(iHndl,subjectStr);
  1486.     }
  1487.  
  1488.     /* copy newsgroups field */
  1489.     
  1490.     offset = Munger(theHndl,0L,"Newsgroups: ",12L,nil,0L);
  1491.     offset2 = Munger(theHndl,offset,CRSTR,1L,nil,0L);
  1492.     if (offset > 0 && offset2 > 0 && (offset2-offset) < 255 ) {
  1493.         BlockMove( (*theHndl)+offset+12,(Ptr) groupStr,(offset2-offset-12));
  1494.         groupStr[offset2-offset-12] = '\0';
  1495.         c2pstr(groupStr);
  1496.         GetDItem(theDlg,3,&iType,&iHndl,&iRect);
  1497.         SetIText(iHndl,groupStr);
  1498.     }
  1499.  
  1500.     /* copy distribution field */
  1501.     
  1502.     offset = Munger(theHndl,0L,"Distribution: ",14L,nil,0L);
  1503.     offset2 = Munger(theHndl,offset,CRSTR,1L,nil,0L);
  1504.     if (offset > 0 && offset2 > 0 && (offset2-offset) < 255 ) {
  1505.         BlockMove( (*theHndl)+offset+14,(Ptr) distStr,(offset2-offset-14));
  1506.         distStr[offset2-offset-14] = '\0';
  1507.         c2pstr(distStr);
  1508.         GetDItem(theDlg,4,&iType,&iHndl,&iRect);
  1509.         SetIText(iHndl,distStr);
  1510.     }
  1511.  
  1512.     HUnlock(theHndl);
  1513.  
  1514.     GetDItem(theDlg,6,&iType,&iHndl,&iRect);
  1515.     SetIText(iHndl,c2pstr(gPrefs.fullName));
  1516.     GetDItem(theDlg,7,&iType,&iHndl,&iRect);
  1517.     SetIText(iHndl,c2pstr(gPrefs.organization));
  1518.     GetDItem(theDlg,8,&iType,&iHndl,&iRect);
  1519.     SetIText(iHndl,c2pstr(gPrefs.address));
  1520.  
  1521.     SelIText(theDlg,3,32767,32767);
  1522.     
  1523.     do
  1524.         ModalDialog(CmdKeyFilter,&item);
  1525.     while (item != okButton && item != cancelButton);
  1526.     
  1527.     GetDItem(theDlg,3,&iType,&iHndl,&iRect);
  1528.     GetIText(iHndl,groupStr);
  1529.     p2cstr(groupStr);
  1530.     GetDItem(theDlg,4,&iType,&iHndl,&iRect);
  1531.     GetIText(iHndl,distStr);
  1532.     p2cstr(distStr);
  1533.     GetDItem(theDlg,5,&iType,&iHndl,&iRect);
  1534.     GetIText(iHndl,subjectStr);
  1535.     
  1536.     GetDItem(theDlg,6,&iType,&iHndl,&iRect);
  1537.     GetIText(iHndl,gPrefs.fullName);
  1538.     p2cstr(gPrefs.fullName);
  1539.     GetDItem(theDlg,7,&iType,&iHndl,&iRect);
  1540.     GetIText(iHndl,gPrefs.organization);
  1541.     p2cstr(gPrefs.organization);
  1542.  
  1543.     GetDItem(theDlg,8,&iType,&iHndl,&iRect);
  1544.     GetIText(iHndl,gPrefs.address);
  1545.     p2cstr(gPrefs.address);
  1546.     
  1547.     strcpy(fromStr,gPrefs.address);
  1548.     strcat(fromStr," (");
  1549.     strcat(fromStr,gPrefs.fullName);
  1550.     strcat(fromStr,")");
  1551.     
  1552.     DisposDialog(theDlg);
  1553.     
  1554.     if (item == cancelButton)
  1555.         return;
  1556.     
  1557.     theInfo = (TwindowInfo *) GetWRefCon(theWind = MakeNewWindow(cPostMessage,true,firstOffset,subjectStr));
  1558.  
  1559.     theInfo->changed = true;
  1560.     IncludeQuote(oldWind,theWind,refStr);
  1561.     AddHeader("Path: ",gPrefs.address,theWind);
  1562.     AddHeader("From: ",fromStr,theWind);
  1563.     AddHeader("Newsgroups: ",groupStr,theWind);
  1564.     AddHeader("Distribution: ",distStr,theWind);
  1565.     AddHeader("Subject: ",(char *)p2cstr(subjectStr),theWind);
  1566.     AddHeader("References:",refStr,theWind);
  1567.     AddHeader("Organization: ",gPrefs.organization,theWind);
  1568.     AddHeader("","",theWind);
  1569.  
  1570.     ShowWindow(theWind);
  1571.  
  1572.     GetPort(&savePort);
  1573.     SetPort(theWind);
  1574.     RedoControls(theWind);
  1575.     FixText(theWind);
  1576.     InvalRect(&theWind->portRect);
  1577.     SetPort(savePort);
  1578. }
  1579.  
  1580.  
  1581. /*    MakeRespond creates a new message template for an e-mail response to
  1582.     a news article.
  1583. */
  1584.  
  1585. void MakeRespond(void)
  1586. {
  1587.     DialogPtr theDlg;
  1588.     short item;
  1589.     TwindowInfo *theInfo;
  1590.     WindowPtr oldWind,theWind;
  1591.     Point firstOffset;
  1592.     GrafPtr savePort;
  1593.     char recipStr[256],subjectStr[256],fromStr[256],refStr[256];
  1594.     short iType;
  1595.     Handle iHndl,theHndl;
  1596.     Rect iRect;
  1597.     extern TPrefRec gPrefs;
  1598.     long offset,offset2,offset3;
  1599.     
  1600.     if (!FrontWindow()) {
  1601.         SysBeep(1);
  1602.         return;
  1603.     }
  1604.     
  1605.     SetCursor(&QDARROW);
  1606.     
  1607.     oldWind = FrontWindow();
  1608.     SetPt(&firstOffset,0,0);
  1609.     GetPort(&savePort);
  1610.     SetPort(oldWind);
  1611.     LocalToGlobal(&firstOffset);
  1612.     SetPort(savePort);
  1613.     
  1614.     SetCursor(&QDARROW);
  1615.  
  1616.     theDlg = GetNewDialog(kRespDlg,nil,(WindowPtr)-1);
  1617.     OutlineOK(theDlg);
  1618.     
  1619.     theInfo = (TwindowInfo *) GetWRefCon(oldWind);
  1620.     theHndl = (Handle) TEGetText((TEHandle)theInfo->data);
  1621.     HLock(theHndl);
  1622.  
  1623.     /* copy subject field */
  1624.     
  1625.     offset = Munger(theHndl,0L,"Subject: ",9L,nil,0L);
  1626.     offset2 = Munger(theHndl,offset,CRSTR,1L,nil,0L);
  1627.     if (offset2 > offset && (offset2-offset) < 255 ) {
  1628.         strcpy(subjectStr,"Re: ");
  1629.         if (strncmp( (*theHndl)+offset+9,"Re: ",4) == 0)
  1630.             offset += 4;
  1631.         BlockMove( (*theHndl)+offset+9,(Ptr) (subjectStr+4),(offset2-offset-9));
  1632.         subjectStr[offset2-offset-9+4] = '\0';
  1633.         c2pstr(subjectStr);
  1634.         GetDItem(theDlg,4,&iType,&iHndl,&iRect);
  1635.         SetIText(iHndl,subjectStr);
  1636.     }
  1637.  
  1638.     /* copy recipient field */
  1639.     
  1640.     offset = Munger(theHndl,0L,"From: ",6L,nil,0L);
  1641.     offset2 = Munger(theHndl,offset,"(",1L,nil,0L);
  1642.     offset3 = Munger(theHndl,offset,CRSTR,1L,nil,0L);
  1643.     if (offset2 < 0 || offset3 < offset2)
  1644.         offset2 = offset3;
  1645.     if (offset > 0 && offset2 > 0 && (offset2-offset) < 255 ) {
  1646.         BlockMove( (*theHndl)+offset+6,(Ptr) recipStr,(offset2-offset-6));
  1647.         recipStr[offset2-offset-6] = '\0';
  1648.         c2pstr(recipStr);
  1649.         GetDItem(theDlg,3,&iType,&iHndl,&iRect);
  1650.         SetIText(iHndl,recipStr);
  1651.     }
  1652.     
  1653.     GetDItem(theDlg,5,&iType,&iHndl,&iRect);
  1654.     SetIText(iHndl,c2pstr(gPrefs.fullName));
  1655.     GetDItem(theDlg,6,&iType,&iHndl,&iRect);
  1656.     SetIText(iHndl,c2pstr(gPrefs.organization));
  1657.     GetDItem(theDlg,7,&iType,&iHndl,&iRect);
  1658.     SetIText(iHndl,c2pstr(gPrefs.address));
  1659.     
  1660.     HUnlock(theHndl);
  1661.  
  1662.     SelIText(theDlg,3,32767,32767);
  1663.  
  1664.     do
  1665.         ModalDialog(CmdKeyFilter,&item);
  1666.     while (item != okButton && item != cancelButton);
  1667.  
  1668.     GetDItem(theDlg,3,&iType,&iHndl,&iRect);
  1669.     GetIText(iHndl,recipStr);
  1670.     p2cstr(recipStr);
  1671.     GetDItem(theDlg,4,&iType,&iHndl,&iRect);
  1672.     GetIText(iHndl,subjectStr);
  1673.     GetDItem(theDlg,5,&iType,&iHndl,&iRect);
  1674.     GetIText(iHndl,gPrefs.fullName);
  1675.     p2cstr(gPrefs.fullName);
  1676.     GetDItem(theDlg,6,&iType,&iHndl,&iRect);
  1677.     GetIText(iHndl,gPrefs.organization);
  1678.     p2cstr(gPrefs.organization);
  1679.  
  1680.     GetDItem(theDlg,7,&iType,&iHndl,&iRect);
  1681.     GetIText(iHndl,gPrefs.address);
  1682.     p2cstr(gPrefs.address);
  1683.  
  1684.     strcpy(fromStr,gPrefs.address);
  1685.     strcat(fromStr," (");
  1686.     strcat(fromStr,gPrefs.fullName);
  1687.     strcat(fromStr,")");
  1688.     
  1689.     DisposDialog(theDlg);
  1690.  
  1691.     if (item == cancelButton)
  1692.         return;
  1693.         
  1694.     theInfo = (TwindowInfo *) GetWRefCon(theWind = MakeNewWindow(cSendMessage,true,firstOffset,subjectStr));
  1695.     theInfo->changed = true;
  1696.     IncludeQuote(oldWind,theWind,refStr);
  1697.  
  1698.     AddHeader("From: ",fromStr,theWind);
  1699.     AddHeader("To: ",recipStr,theWind);
  1700.     AddHeader("Subject: ",(char *)p2cstr(subjectStr),theWind);
  1701.     AddHeader("References:",refStr,theWind);
  1702.     AddHeader("Organization: ",gPrefs.organization,theWind);
  1703.     AddHeader("","",theWind);
  1704.  
  1705.     ShowWindow(theWind);
  1706.  
  1707.     GetPort(&savePort);
  1708.     SetPort(theWind);
  1709.     RedoControls(theWind);
  1710.     FixText(theWind);
  1711.     InvalRect(&theWind->portRect);
  1712.     SetPort(savePort);
  1713. }
  1714.  
  1715.  
  1716. /*    MakePost creates a new article template for a new posting to a user-
  1717.     specified newsgroup.
  1718. */
  1719.  
  1720. void MakePost(void)
  1721. {
  1722.     DialogPtr theDlg;
  1723.     short item;
  1724.     TwindowInfo *theInfo;
  1725.     WindowPtr theWind;
  1726.     Point firstOffset;
  1727.     short iType;
  1728.     Handle iHndl;
  1729.     Rect iRect;
  1730.     char subjectStr[256],groupStr[256],fromStr[256],distStr[256];
  1731.     GrafPtr savePort;
  1732.     extern TPrefRec gPrefs;
  1733.  
  1734.     SetCursor(&QDARROW);
  1735.     
  1736.     if (CautionAlert(kPostAlert,nil) != okButton)
  1737.         return;
  1738.  
  1739.     SetCursor(&QDARROW);
  1740.  
  1741.     theDlg = GetNewDialog(kPostDlg,nil,(WindowPtr)-1);
  1742.     OutlineOK(theDlg);
  1743.  
  1744.     GetDItem(theDlg,6,&iType,&iHndl,&iRect);
  1745.     SetIText(iHndl,c2pstr(gPrefs.fullName));
  1746.     GetDItem(theDlg,7,&iType,&iHndl,&iRect);
  1747.     SetIText(iHndl,c2pstr(gPrefs.organization));
  1748.     GetDItem(theDlg,8,&iType,&iHndl,&iRect);
  1749.     SetIText(iHndl,c2pstr(gPrefs.address));
  1750.  
  1751.     SelIText(theDlg,3,32767,32767);
  1752.  
  1753.     do
  1754.         ModalDialog(CmdKeyFilter,&item);
  1755.     while (item != okButton && item != cancelButton);
  1756.  
  1757.     GetDItem(theDlg,3,&iType,&iHndl,&iRect);
  1758.     GetIText(iHndl,groupStr);
  1759.     p2cstr(groupStr);
  1760.     GetDItem(theDlg,4,&iType,&iHndl,&iRect);
  1761.     GetIText(iHndl,distStr);
  1762.     p2cstr(distStr);
  1763.     GetDItem(theDlg,5,&iType,&iHndl,&iRect);
  1764.     GetIText(iHndl,subjectStr);
  1765.     GetDItem(theDlg,6,&iType,&iHndl,&iRect);
  1766.     GetIText(iHndl,gPrefs.fullName);
  1767.     p2cstr(gPrefs.fullName);
  1768.     GetDItem(theDlg,7,&iType,&iHndl,&iRect);
  1769.     GetIText(iHndl,gPrefs.organization);
  1770.     p2cstr(gPrefs.organization);
  1771.     
  1772.     GetDItem(theDlg,8,&iType,&iHndl,&iRect);
  1773.     GetIText(iHndl,gPrefs.address);
  1774.     p2cstr(gPrefs.address);
  1775.  
  1776.     strcpy(fromStr,gPrefs.address);
  1777.     strcat(fromStr," (");
  1778.     strcat(fromStr,gPrefs.fullName);
  1779.     strcat(fromStr,")");
  1780.  
  1781.     DisposDialog(theDlg);
  1782.  
  1783.     if (item == cancelButton)
  1784.         return;
  1785.         
  1786.     SetPt(&firstOffset,kOffLeft,kOffTop);
  1787.     theInfo = (TwindowInfo *) GetWRefCon(theWind = MakeNewWindow(cPostMessage,true,firstOffset,subjectStr));
  1788.     theInfo->changed = true;
  1789.     
  1790.     AddHeader("Path: ",gPrefs.address,theWind);
  1791.     AddHeader("From: ",fromStr,theWind);
  1792.     AddHeader("Newsgroups: ",groupStr,theWind);
  1793.     AddHeader("Distribution: ",distStr,theWind);
  1794.     AddHeader("Subject: ",(char *)p2cstr(subjectStr),theWind);
  1795.     AddHeader("Organization: ",gPrefs.organization,theWind);
  1796.     AddHeader("","",theWind);
  1797.     
  1798.     ShowWindow(theWind);
  1799.  
  1800.     GetPort(&savePort);
  1801.     SetPort(theWind);
  1802.     RedoControls(theWind);
  1803.     FixText(theWind);
  1804.     InvalRect(&theWind->portRect);
  1805.     SetPort(savePort);
  1806. }
  1807.  
  1808.  
  1809. /*    IncludeQuote indents and includes the article to which the user is responding
  1810.     in the new article template.
  1811. */
  1812.  
  1813. void IncludeQuote(WindowPtr parentWindow,WindowPtr newWindow,char *refStr)
  1814. {
  1815.     Handle text,oldText;
  1816.     long teLength;
  1817.     TwindowInfo *info;
  1818.     long offset,offset2;
  1819.     char article[256],sender[256];
  1820.     char mungeStr[256];
  1821.     
  1822.     info = (TwindowInfo *) GetWRefCon(parentWindow);
  1823.     oldText = text = (Handle) TEGetText((TEHandle)info->data);
  1824.     teLength = (**((TEHandle)info->data)).teLength;
  1825.     
  1826.     info = (TwindowInfo *) GetWRefCon(newWindow);
  1827.     
  1828.     strcpy(mungeStr,CRSTR);
  1829.     strcat(mungeStr,"Message-ID:");
  1830.     offset = Munger(oldText,0,mungeStr,12L,nil,0L);
  1831.     if (offset >= 0) {
  1832.         offset += 12;
  1833.         offset2 = Munger(oldText,offset,CRSTR,1L,nil,0L);
  1834.         if (offset2 < 0)
  1835.             offset2 = offset;
  1836.         strncpy( article,(char *)*oldText+offset,offset2-offset);
  1837.         *(article+offset2-offset) = '\0';
  1838.     }
  1839.     else
  1840.         strcpy(article,"");
  1841.         
  1842.     strcpy(mungeStr,CRSTR);
  1843.     strcat(mungeStr,"From:");
  1844.     offset = Munger(oldText,0,mungeStr,6L,nil,0L);
  1845.     if (offset >= 0) {
  1846.         offset += 6;
  1847.         offset2 = Munger(oldText,offset,CRSTR,1L,nil,0L);
  1848.         if (offset2 < 0)
  1849.             offset2 = offset;
  1850.         strncpy( sender,(char *)*oldText+offset,offset2-offset);
  1851.         *(sender+offset2-offset) = '\0';
  1852.     }
  1853.     else
  1854.         strcpy(sender,"...");
  1855.  
  1856.     strcpy(mungeStr,CRSTR);
  1857.     strcat(mungeStr,"References:");        
  1858.     offset = Munger(oldText,0,mungeStr,12L,nil,0L);
  1859.     if (offset >= 0) {
  1860.         offset += 12;
  1861.         offset2 = Munger(oldText,offset,CRSTR,1L,nil,0L);
  1862.         if (offset2 < 0)
  1863.             offset2 = offset;
  1864.         strncpy( refStr,(char *)*oldText+offset,offset2-offset);
  1865.         *(refStr+offset2-offset) = '\0';
  1866.     }
  1867.     else
  1868.         strcpy(refStr,"");
  1869.     strcat(refStr,article);
  1870.     
  1871.     if (MyHandToHand(&text) != noErr)
  1872.         return;
  1873.     MySetHandleSize(text,teLength);
  1874.     if (MyMemErr() != noErr)
  1875.         return;
  1876.     
  1877.     strcpy(mungeStr,CRSTR);
  1878.     strcat(mungeStr,CRSTR);
  1879.     offset = Munger(text,0,mungeStr,2L,nil,0L);
  1880.     offset = Munger(text,0,nil,offset,"*",0L);
  1881.     strcpy(mungeStr,CRSTR);
  1882.     strcat(mungeStr,"> ");
  1883.     while ( (offset = Munger(text,offset,CRSTR,1L,mungeStr,3L)) >= 0)
  1884.         ;
  1885.     offset = Munger(text,0,nil,0L,"In article",10L);
  1886.     offset = Munger(text,offset,nil,0L,article,strlen(article));
  1887.     offset = Munger(text,offset,nil,0L,",",1L);
  1888.     offset = Munger(text,offset,nil,0L,sender,strlen(sender));
  1889.     offset = Munger(text,offset,nil,0L," writes:",8L);
  1890.     
  1891.     teLength = GetHandleSize(text);
  1892.     HLock(text);
  1893.     TESetText(*text,teLength,(TEHandle)info->data);
  1894.     TESetSelect(0L,0L,(TEHandle)info->data);
  1895.     HUnlock(text);
  1896.     MyDisposHandle(text);
  1897. }
  1898.  
  1899.  
  1900. /*    AddHeader adds a header of name hName and contents hContents to the
  1901.     TextEdit field in window newWindow.
  1902. */
  1903.  
  1904. void AddHeader(char *hName,char *hContents,WindowPtr newWindow)
  1905. {
  1906.     TEHandle theTE;
  1907.     
  1908.     theTE = (TEHandle) ((TwindowInfo *)GetWRefCon(newWindow))->data;
  1909.     
  1910.     TEInsert(hName,strlen(hName),theTE);
  1911.     TEInsert(hContents,strlen(hContents),theTE);
  1912.     TEInsert(CRSTR,1,theTE);
  1913. }
  1914.  
  1915.  
  1916. /*    If user authentication were important, this routine would check to make
  1917.     sure that the "From:" header was not changed before allowing the article
  1918.     to be posted.
  1919. */
  1920.  
  1921. Boolean CheckHeader(TwindowInfo *info)
  1922. {
  1923. #pragma unused (info)
  1924.     
  1925.     /* no user authentication, so don't have to check header */
  1926.     
  1927.     return true;
  1928. }
  1929.