ftp.nice.ch/pub/next/connectivity/news/Alexandra-0.9.s.tar.gz#/alex/Composer.m

This is Composer.m in view mode; [Download] [Up]

#import <appkit/appkit.h>
#import <misckit/misckit.h>
#import <misckit/MiscDateView.h>
#import <foundation/foundation.h>

#import "Alexandra.h"
#import "Composer.h"
#import "NNTP.h"
#import "descriptors.h"
#import "ArticleViewControl.h"
#import "ArticleSet.h"
#import "NntpPanelControl.h"
#import "plain-subject.h"
#include <ctype.h>
#include <dbkit/DBTableView.h>
#import "NSString+MIME.h"

#define	OK_POSTED	240

static MiscList	*myComposers;
static XTDispatchAction *emacs_action;
static XTDispatchAction *none_action;


static void writeNSDataToASCIIStream(NXStream *stream, void *item, void *userInfo)
{
	NSData	*data = item;
	
	if(data == nil)
		{
		NXPrintf(stream, "*nil*");
		}
	else
		{
		NXWrite(stream, [data bytes], [data length]);
		}
}



@implementation Composer

//-----------------------------------------------------------
// Klassen Methoden, Verwalten der Composer Liste 
//-----------------------------------------------------------

+ initialize
{
	char buf[MAXPATHLEN + 1];
	const char *userBindings;
	
	if(self==[Composer class])
	{
		myComposers=[[MiscList alloc] init];
		emacs_action=[[XTDispatchAction alloc] init];
		none_action=[[XTDispatchAction alloc] init];
		
		if([[NXBundle mainBundle] getPath:buf forResource:"emacs" ofType:"keys"])
			[emacs_action loadFromFile:buf estream:nil];
		
		if(userBindings=[NXApp defaultValue:"KeyString"]){
			[emacs_action addBindings:userBindings estream:nil];
			[none_action addBindings:userBindings estream:nil];
		}
	NXRegisterPrintfProc('z', &writeNSDataToASCIIStream, [(Object *)self zone]);
	}
	return self;
}


+ newWindowForServer:(NNTP *)server;
	{
	Composer	*new;
	
	new=[[self alloc] initForServer:server];
	[myComposers addObject:new];
	return new;
	}	
	

+ removeComposer:(Composer *)composer;
	{
	[myComposers removeObject:composer];
	return self;
	}
	

+ killComposerForServer:(NNTP *)server;
	{
	Composer *c;
	
	for(c=[myComposers setLastObject];c;c=[myComposers setPreviousObject])
		if([c nntpServer]==server)
			[c free];
	return self;
	}

+ confirmTermination
{
	int r1,r2;
   int j=[myComposers count];
	if(j>0)
		do{
			r1=NXRunAlertPanel("ALEXANDRA","There are unsend messages.","Review unsend","Quit anyway","Cancel");
			if(r1==NX_ALERTOTHER)
				return nil;
			else if(r1==NX_ALERTALTERNATE){
				int i;
				for(i=j-1;i>=0;i--)
					[[myComposers objectAt:i] free];
				
				return self;
			}
			else{
				int i;
				BOOL canceled=FALSE;
				for(i=j-1;i>=0;i--){
					id aComposer=[myComposers objectAt:i];
					[[aComposer window] makeKeyAndOrderFront:self];
					r2=NXRunAlertPanel("ALEXANDRA","Send this message?","Send","Don't send","Cancel");
					if(r2==NX_ALERTOTHER){
						canceled=TRUE;
						break;
					}
					else if(r2==NX_ALERTDEFAULT)
						[aComposer post:self];
					else
						[aComposer free];
				}
				if(canceled)
					j=[myComposers count];
				else
					return self;
			}
		} while(j>0);
	
	return self;
}			
//-----------------------------------------------------------
// INIT & FREE 
//-----------------------------------------------------------

- initForServer:(NNTP *)theServer;
	{
	float char78Width;
	NXRect textFrame;
	NXRect windowFrame;
	NXAtom	ids[]={[KVPair keyIdentifier],[KVPair valueIdentifier],NULL};
	
	if([NXApp loadNibSection:"Composer.nib" owner:self withNames:NO]==nil)
		EM_ERROR(EGENFileNotFound,"Composer.nib",NULL);
	[self setNntpServer:theServer];
	headerController=[[[MiscTableController alloc] init] setTableView:headerTable withIdentifiers:ids];
	[self getDefaults];

	char78Width=78*[[theText font] getWidthOf:"A"];
	[theText getFrame:&textFrame];
	[[theText window] getFrame:&windowFrame];
	char78Width+=NX_WIDTH(&windowFrame)-NX_WIDTH(&textFrame);

	[[theText window] sizeWindow:char78Width :NX_HEIGHT(&windowFrame)];

   if([NXApp defaultBoolValue:DEFAULT_APPEND_SIG]){
      NXStream *signatureFile;
      const char *home=NXHomeDirectory();
      char *filename;

      filename=(char *)NXZoneCalloc(MYZONE,(strlen(home)+12),sizeof(char));
	  sprintf(filename,"%s/.signature",home);

      if((signatureFile=NXMapFile(filename,NX_READONLY))!=NULL){
  		[theText readText:signatureFile];
		[theText setSel:0:0];
      	[theText replaceSel:"\n-- \n"];
  		[theText setSel:0:0];
    	NXClose(signatureFile);
      }
		else
			NXRunAlertPanel("Alexandra","Cannot append signature."
					" File `~/.signature' not found.","Cancel",NULL,NULL);
      NXZoneFree(MYZONE,filename);
   }


	[[theText window] makeKeyAndOrderFront:self];
	
	return self;
	}



- getDefaults
	{
	KVPair		*emptypair=[[KVPair allocFromZone:MYZONE] init];
	char		defname[200];
	const char	*d;
	int			i,n=[NXApp defaultIntValue:DEFAULT_XHEADER_COUNT];

	[headerController empty:nil];
	for(i=0;i<n;i++)
		{
		sprintf(defname,DEFAULT_XHEADERS,i+1);
		d=[NXApp defaultValue:defname];
		if(d)
			[headerController addRow:[[KVPair alloc] init:d delimiter:':']];
		else
			[headerController addRow:emptypair];
		}
	[emptypair free];
   	return self;
	}
  
  
- free
{
   [[self class] removeComposer:self];
   [[[headerTable window] close] free];
   [[window close] free];

   if(subject!=NULL)
      free(subject);
   if(newsgroups!=NULL)
      free(newsgroups);
   if(references!=NULL)
      free(references);
   if(theQuotingStream!=NULL)
      NXCloseMemory(theQuotingStream,NX_FREEBUFFER);
   return [super free];
}


- awakeFromNib
	{
	
	if(strcmp([NXApp defaultValue:DEFAULT_KEY_BASE],"emacs")==0)
		local_action=emacs_action;
	else
		local_action=none_action;
		
	theText=[theText docView];
	[theText setMonoFont:YES];
	[theText setFont:	[Font userFixedPitchFontOfSize:0 
							matrix:NX_FLIPPEDMATRIX]];
		
	[theText setInitialAction:local_action];
	[headerForm setNextText:theText];
	return self;
	}


//-----------------------------------------------------------
// quoting 
//-----------------------------------------------------------

- followup:sender
{
   char *newSubject,*oldSubject;
   BOOL wasReply;

   if(subject==NULL)
      return self;

	if(fup)
		{
		if(NXRunAlertPanel("ALEXANDRA", "The author of this article has asked to respond via e-mail. Are you sure that you want to post to the newsgroup?", "Cancel", "Post", NULL) == NX_ALERTDEFAULT)
			return self;
		}
   oldSubject=plain_subject(subject,&wasReply);
   newSubject=(char *)NXZoneCalloc(MYZONE,(strlen(subject)+5),sizeof(char));
   sprintf(newSubject,"Re: %s",oldSubject);
   [subjectCell setStringValueNoCopy:newSubject shouldFree:YES];

   [newsgroupCell setStringValue:newsgroups];
   [followupButton setIcon:[followupButton altIcon]];
   [followupButton setTitle:[followupButton altTitle]];
   [followupButton setAction:@selector(includeArticle:)];
   if(strlen(references)>0)
      insert_refs=TRUE;
   return self;
}

- includeArticle:sender
{
	char *buf;
	int len,maxlen;
	
    if(theQuotingStream!=NULL){
	 	 NXGetMemoryBuffer(theQuotingStream,&buf,&len,&maxlen);
       [theText setSel:0:0];
       [theText replaceSel:buf length:len];
    }
    [followupButton setEnabled:FALSE];
    return self;
}

- post:sender
{
   char month_name[12][4]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep",
                        "Oct","Nov","Dec"};
   char *mime_header[]={"MIME-Version: 1.0\nContent-Type: text/plain; charset=us-ascii\nContent-Transfer-Encoding: 7bit\n", "MIME-Version: 1.0\nContent-Type: text/plain; charset=iso-8859-1\nContent-Transfer-Encoding: 8bit\n"};
   char *a2String, *newgroupsCleanedUp, *s, *d;
   NXStream *theStream;
   long bodyPos;
   int textLength;
   int line;
   int pos;
   NXBreakArray	*theBreaks=0;
   int nbreaks;
   NXLineDesc *breaks;
   BOOL asciiBody;
   BOOL userDefdFrom=NO;
   int statusCode, c1, c2;
   MiscList *headers;
   KVPair	*pair;
   NSString *encodedField, *trimmedRefs;
   NXStream *textStream;

   if([newsgroupCell stringValue] == NULL || [newsgroupCell stringValue][0]=='\0'){
      NXRunAlertPanel("ALEXANDRA","No Newsgroups header field body. Can't post.",NULL,NULL,NULL);
      return nil;
   }
   if([subjectCell stringValue][0]=='\0'){
      NXRunAlertPanel("ALEXANDRA","No Subject header field body. Can't post.",NULL,NULL,NULL);
      return nil;
   }
   if([theText textLength]==0){
      NXRunAlertPanel("ALEXANDRA","No article body. Can't post.",NULL,NULL,NULL);
      return nil;
   }
   if(theQuotingStream != NULL)
   		{
		textStream = [theText stream];
		NXSeek(textStream, 0, NX_FROMSTART);
		NXSeek(theQuotingStream, 0, NX_FROMSTART);
		while((NXAtEOS(textStream) == NO) && (NXAtEOS(theQuotingStream) == NO))
			{
			if((c1 = NXGetc(textStream)) != (c2 = NXGetc(theQuotingStream)))
				break;
			}
		if(NXAtEOS(textStream) && NXAtEOS(theQuotingStream))
			{
			NXRunAlertPanel("ALEXANDRA", "You must add text to the quoted passage before you can post.", "OK", NULL, NULL);
			return nil;
			}
		}
   
   if([expireButton state] && ([dateView isDateValid:self]==FALSE))
      return nil;
	
	[headerTable endEditing];
	
   theStream=NXOpenMemory(NULL,0,NX_READWRITE);
   NXSeek(theStream,0,NX_FROMSTART);

   //Write MIME-Header
   asciiBody=[self asciiBody];
   NXPrintf(theStream,"%s",mime_header[(asciiBody == YES) ? 0 : 1]);
   
   // Write Newsgroups: header (silently remove all spaces)
   newgroupsCleanedUp = NXCopyStringBufferFromZone([newsgroupCell stringValue], MYZONE);
   for(s = d = newgroupsCleanedUp; *s != '\0'; s++)
		if(*s != ' ')
			*d++ = *s;
   NXPrintf(theStream,"Newsgroups: %s\n", newgroupsCleanedUp);
   NXZoneFree(MYZONE, newgroupsCleanedUp);	
   
   // Write Subject: header
   NXPrintf(theStream,"Subject: %z\n", [[NSString stringWithCString:
     [subjectCell stringValue]] dataForMIMEHeaderField]);

   // Write References: header
   if(insert_refs==TRUE)
   	  {
	  trimmedRefs = [[NSString stringWithCString:references] stringByTrimmingReferencesToLength:1000 - 2 - 12];
	  if(trimmedRefs == nil)
	  	{
		if(NXRunAlertPanel("ALEXANDRA", "There is something wrong with the references header in the original article. If you decide to post this followup it will not contain references to other articles of this thread.", "Post", "Cancel", NULL) != NX_ALERTDEFAULT)
			return nil;
		}
	  else
	    {
	    NXPrintf(theStream,"References: %z\n",[trimmedRefs dataForHeaderFieldBody]);
		}
	  }

	headers=[headerController rows];
	for(pair=[headers setFirstObject]; pair; pair=[headers setNextObject])
		if([pair hasValue])
			{
			if(!strcasecmp([pair key],"from"))
				userDefdFrom=YES;
			NXPrintf(theStream,"%s: %z\n",[pair key],
			  [[NSString stringWithCString:[pair value]] dataForMIMEHeaderField]);
			}

   // Write Expires: header
   if([expireButton state]){
      int day=[dateView getDay:self];
      const char *month=month_name[[dateView getMonth:self]-1];
      int year=[dateView getYear:self];

      NXPrintf(theStream,"Expires: %d %s %d 00:00:00 GMT\n",day,month,year);
   }

   if(!userDefdFrom){
	    a2String=[[FromHeaderController new] validFromAddress];
		if(!a2String)
			{
	      	NXCloseMemory(theStream,NX_FREEBUFFER);
			return nil;
			}
	    NXPrintf(theStream,"From: %s\n",[[NSString stringWithCString:a2String]
		  dataForMIMEHeaderField]);
		free(a2String);
   }
   NXPrintf(theStream,"X-Newsreader: %s.app (%s)\n",
   						[NXApp appName],[NXApp appVersion]);

   //Write end of header
   NXPutc(theStream,'\n');
   bodyPos=NXTell(theStream);
   
   /*write body*/
   textLength=[theText textLength];
   object_getInstanceVariable(theText, "theBreaks", (void **)&theBreaks);
   nbreaks = theBreaks->chunk.used/sizeof(NXLineDesc);
   breaks = theBreaks->breaks;

   for(pos=0,line=0;line<nbreaks && pos<textLength;line++) {
      int lineChange;
      int endParagraph;
      int len,nlen;
      char *buf,*realbuf;

      lineChange = breaks[line] & 0x8000;
      endParagraph = breaks[line] & 0x4000;
		
      len = breaks[line] & 0x3fff;
      buf = malloc(len+2);
      realbuf=buf;
      buf++;
      nlen = [theText getSubstring:buf start:pos length:len];
      
      if(buf[0]=='.'){
         buf--;
         nlen++;
         buf[0]='.';
      }

      if((nlen>0)&&(buf[nlen-1]!='\n')){
         if(buf[nlen-1]=='\0')
            buf[nlen-1]='\n';
         else{
            buf[nlen] = '\n';
            nlen++;
         }
      }

      NXWrite(theStream,buf,nlen);	
      free(realbuf);
      pos+=len;
      if (lineChange!=0) {
         /* "if the line change bit is set, the descriptor is
          * the first field of a NXHeightChange...." */
         line += sizeof(NXHeightInfo) / sizeof(NXLineDesc);
      }
   }

   statusCode=[nntpServer postArticle:theStream];
   if(statusCode==OK_POSTED){
      [window close];
      NXCloseMemory(theStream,NX_FREEBUFFER);
      [self free];
		return self;
   }
	
   return nil;
}

- preparePostTo:(List *)newsgroupList selArticle:(Article *)anArticle 
	inView:(ArticleViewControl *)controlView;
{
   int i,j,strlength;
   char *newsgroup_str;
   char *ptr;
   const char *newsgroup;
   char *msg_id,*refs;

   //Make newsgrouplist string and set Newsgroups: field
   strlength=0;
   j=[newsgroupList count];
   for(i=0;i<j;i++)
      strlength += strlen([[newsgroupList objectAt:i] stringValue]);
   newsgroup_str=(char *)malloc((strlength+j)*sizeof(char));
   ptr=newsgroup_str;
   for(i=0;i<j;i++){
      newsgroup=[[newsgroupList objectAt:i] stringValue];
      strcpy(ptr,newsgroup);
      ptr+=strlen(newsgroup);
      if(i<j-1)
         *ptr=',';
      ptr++;
   }
   [newsgroupCell setStringValueNoCopy:newsgroup_str shouldFree:YES];

   //remember selected article fields
   subject=NULL;
   if(anArticle==nil)
      [followupButton setEnabled:FALSE];
   else{
      subject=NXCopyStringBuffer([anArticle header]->fieldBody[SUBJECT]);
      
      //where to followup?
      if([anArticle header]->fieldBody[FOLLOWUP_TO]!=NULL)
	  	  {
          newsgroups=NXCopyStringBuffer([anArticle header]->fieldBody[FOLLOWUP_TO]);
		  if(strcmp(newsgroups, "poster") == 0)
		  	{
			fup = YES;
			NX_FREE(newsgroups); newsgroups = NULL;
            if([anArticle header]->fieldBody[NEWSGROUPS]!=NULL)
                newsgroups=NXCopyStringBuffer([anArticle header]->fieldBody[NEWSGROUPS]);
			}
		  }
      else
         if([anArticle header]->fieldBody[NEWSGROUPS]!=NULL)
            newsgroups=NXCopyStringBuffer([anArticle header]->fieldBody[NEWSGROUPS]);
      
      //References field
      i=0; j=0;
      refs=[anArticle header]->fieldBody[REFS];
      msg_id=[anArticle header]->fieldBody[MSG_ID];
      if(refs!=NULL)
         i=strlen(refs);
      if(msg_id!=NULL)
         j=strlen(msg_id);
      references=(char *)malloc((i+j+3)*sizeof(char));
      references[0]='\0';
      if(refs!=NULL)
         strcpy(references,refs);
      if(msg_id!=NULL){
         if(strlen(references)>0)
            strcat(references," ");
         strcat(references,msg_id);
      }
      insert_refs=FALSE;

      // Copy article body 
      theQuotingStream=NXOpenMemory(NULL,0,NX_READWRITE);
      if([anArticle header]->fieldBody[FROM]!=NULL)
         NXPrintf(theQuotingStream,"%s wrote:\n",[anArticle header]->fieldBody[FROM]);
      [controlView writeQuotedText:theQuotingStream];
   }
   //set DateViews date as today
   [dateView getTodaysDate:self];

   return self;
}

- window
{
   return window;
}

- setNntpServer:(NNTP *)nntp
{
   nntpServer=nntp;
   return self;
}


- (NNTP *)nntpServer;
{
   return nntpServer;
}


- printText:sender
{
   if([theText textLength]!=0){
		[[NXApp printInfo] setHorizPagination:NX_FITPAGINATION];
      [theText printPSCode:self];
	}
	
   return self;
}


- (BOOL)asciiBody
	{
	NXStream	*textStream;
	int			len,maxlen;
	char		*p;
	
	textStream= NXOpenMemory(NULL,0,NX_WRITEONLY);
	[theText writeText:textStream];
	NXGetMemoryBuffer(textStream,&p,&len,&maxlen);
	for(;len>0;p++,len--)
		if(!NXIsAscii(*p))
			break;
	NXCloseMemory(textStream,NX_FREEBUFFER);
	return (len<=0);
	}
	

- theText
{
   return theText;
}


- windowWillClose:sender
{
   if([theText textLength]!=0){
      int answer=NXRunAlertPanel("ALEXANDRA","Message was not posted.","Close Anyway", "Cancel",NULL);
   if(answer==NX_ALERTALTERNATE)
      return nil;
   }
   return [self free];
}


- windowWillMiniaturize:sender toMiniwindow:miniwindow
{
   [sender setMiniwindowIcon:"respond"];
   return self;
}


- windowWillReturnFieldEditor:sender toObject:client
{
	return [XText newFieldEditorFor:sender
				initialAction:local_action
				estream:nil];
}

- rot13:sender
{
	char *buf;
	int i,j=[theText textLength];
	
	buf=malloc((j+2)*sizeof(char));
	[theText getSubstring:buf start:0 length:j];
	buf[j]='\0';
	
	for(i=0;i<j;i++) {
   	if(islower(buf[i]))
			buf[i]=(((buf[i]-'a')+13)%26)+'a';
      else if(isupper(buf[i]))
		  	buf[i]=(((buf[i]-'A')+13)%26)+'A';
   }

	[theText setText:buf];
	
	free(buf);
	
	return self;
}





@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.