This is Send.m in view mode; [Download] [Up]
/* -*-C-*-
*******************************************************************************
*
* File: Send.m
* RCS: /usr/local/sources/CVS/EnhanceMail/Send.m,v 1.19 1998/07/12 10:49:28 tom Exp
* Description:
* Author: Carl Edman
* Created: Fri Oct 13 11:48:05 1995
* Modified: Sun Mar 9 15:42:36 1997 (Tom Hageman)
* Language: C
* Package: N/A
* Status: Experimental (Do Not Distribute)
*
* (C) Copyright 1995, but otherwise this file is perfect freeware.
*
*******************************************************************************
*/
#import <ctype.h>
#import "EnhanceMail.h"
#import "Send.h"
#import "SimpleString.h"
#import "Preferences.h"
#import "XFace.h"
#import "regexp.h"
static HashTable *SignatureHash=nil;
static HashTable *QuotationHash=nil;
static HashTable *ReplyBoxHash=nil;
static HashTable *InReplyToHash=nil;
static BOOL match_firstname(const char *str, const char **start, int *len)
{
static regexp *namerx = 0;
int i;
if (!(str && *str)) return NO;
if (!namerx)
namerx = regcomp("^([^ \t_.%@,!]*[^ \t_%@,!])[^,!]*$|.*[,!][ \t]*([^ \t_.%@,!]*[^ \t_%@,!])");
if (!namerx) return NO;
if (!regexec(namerx, str)) return NO;
for (i = 1; i <= 2; i++)
{
if (namerx->endp[i] > namerx->startp[i])
{
(*start) = namerx->startp[i];
(*len) = namerx->endp[i] - namerx->startp[i];
return YES;
}
}
return NO;
}
/* RFC822 Date:
^( *(Mon|Tue|Wed|Thu|Fri|Sat|Sun) *, )? *([0-9][0-9]?) +(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) +([0-9][0-9][0-9]?[0-9]?) +([0-9]?[0-9]:[0-9][0-9](:[0-9][0-9])?) +(UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|1[A-Z]|\+[0-9][0-9][0-9][0-9]|-[0-9][0-9][0-9][0-9])
*/
static BOOL _match_date(const char *str, const char **start, int *len, int sub)
{
static regexp *daterx = 0;
if (!(str && *str)) return NO;
if (!daterx)
daterx = regcomp("^(([ \t]*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)[ \t]*,[ \t])?[ \t]*([0-9][0-9]?)[ \t]+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[ \t]+([0-9][0-9][0-9]?[0-9]?))[ \t]+([0-9]?[0-9]:[0-9][0-9](:[0-9][0-9])?)");
if (!daterx) return NO;
if (!regexec(daterx, str)) return NO;
(*start) = daterx->startp[sub];
(*len) = daterx->endp[sub] - daterx->startp[sub];
return YES;
}
static inline BOOL match_date(const char *str, const char **start, int *len)
{
return _match_date(str, start, len, 1);
}
static inline BOOL match_time(const char *str, const char **start, int *len)
{
return _match_date(str, start, len, 7);
}
static void append_initials(id s, const char *name, int nameLength)
{
const unsigned char *d = name, *e;
for (e = d + nameLength; d < e; d++)
{
if (NXIsAlpha(*d))
{
BOOL wasUpper = NXIsUpper(*d);
[s appendString:d length:1];
while ((++d < e) && NXIsAlpha(*d))
{
// Handle names like "McDonald" => "MD", instead of just "M".
BOOL isUpper = NXIsUpper(*d);
if (!wasUpper && isUpper) [s appendString:d length:1];
wasUpper = isUpper;
}
}
if (*d == '@') break; // Consider only name part of email address.
}
}
// Handle a single escape sequence. assumption: c points to escape char.
static const char *handle_escape(const char *c, const char *end, id s, id mes, regexp *rx)
{
const char *d;
char *m;
int n;
if (c >= end) return c;
switch (*c++)
{
case '{': /* %{Header} expands to value for header. */
for(d=c;(c<end) && (*c!='}');c++);
m=alloca(c-d+1);
strncpy(m,d,c-d);
m[c-d]='\0';
c++;
if ((d = [mes headerValueForKey:m]))
[s appendString:d];
break;
case '!': /* "%!ab": conditional where escape `b' is expanded
only when escape `a' expands to an empty string. */
n = [s length];
c = handle_escape(c, end, s, mes, rx);
c = handle_escape(c, end, ([s length] > n) ? nil : s, mes, rx);
break;
case '(': // Grouping; useful in conditionals.
while (c < end && *c != ')')
{
if (*c == '%' && (c + 1) < end)
{
c = handle_escape(++c, end, s, mes, rx);
}
else
{
[s appendChar:*(c++)];
}
}
c++;
break;
case 'd':
if (match_date([mes headerValueForKey:"Date"], &d, &n))
[s appendString:d length:n];
break;
case 't':
if (match_time([mes headerValueForKey:"Date"], &d, &n))
[s appendString:d length:n];
break;
case 'f':
d = [mes headerValueForKey:"From"];
if (!EnhanceGetUsername(d, &d, &n)) n = strlen(d);
[s appendString:d length:n];
break;
case 'F': // First name.
d = [mes headerValueForKey:"From"];
if (!EnhanceGetUsername(d, &d, &n)) n = strlen(d);
m = alloca(n+1);
d = strncpy(m, d, n);
m[n] = '\0';
match_firstname(d, &d, &n);
[s appendString:d length:n];
break;
case 'i': // Initials
if (EnhanceGetUsername([mes headerValueForKey:"From"], &d, &n))
append_initials(s, d, n);
else
[s appendChar:'-'];
break;
case 'n':
case 'N':
[s appendChar:'\n'];
break;
case 'T':
[s appendChar:'\t'];
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
for(n=*(c-1)-'0';isdigit(*c) && (c<end);c++) n=n*10+(*c-'0');
if (n<NSUBEXP && rx && rx->startp[n] && rx->endp[n])
[s appendString:rx->startp[n] length:rx->endp[n]-rx->startp[n]];
break;
default:
[s appendChar:*(c-1)];
break;
}
return c;
}
// Interpret all escapes in a string.
static id interpret_escapes(const char *c, id mes, regexp *rx)
{
id s = [[SimpleString alloc] init];
const char *end = c + strlen(c);
while (c < end)
{
if (*c == '%' && (c + 1) < end)
{
c = handle_escape(++c, end, s, mes, rx);
}
else
{
[s appendChar:*(c++)];
}
}
return s;
}
///#ifndef KANJI
static id quote_roman_text(const char *text,int length,id mes)
{
id s=[[SimpleString alloc] init],prefix=nil;
int linebeg=0,i;
int maxlen=[MailMessage lineLength];
const char *line,*end,*word,*tmp,*prebeg,*preend;
regexp *rx=regcomp(EnhanceQuoteRegex);
if (!rx) return s;
prebeg=preend=line=text;
end=line+length;
while(line<end)
{
if (!regexec(rx,line)) for(i=0;i<NSUBEXP;i++) rx->startp[i]=rx->endp[i]=line;
word=rx->endp[0];
if ((prefix==nil) ||
((word-line)!=(preend-prebeg)) ||
(strncmp(line,prebeg,preend-prebeg)!=0))
{
prebeg=line;
preend=word;
if (prefix!=nil) prefix=[prefix free];
prefix=interpret_escapes(EnhanceQuotePrefix, mes, rx);
if (linebeg!=[s length]) linebeg=[s appendChar:'\n'];
}
while((word<end) && ((*word==' ')||(*word=='\t'))) word++;
for(line=word;(line<end)&&(*line!='\n')&&(*line!='\r');line++);
if ((*word=='\n')||(*word=='\r'))
{
if (linebeg!=[s length]) [s appendChar:'\n'];
[s appendSimpleString:prefix];
linebeg=[s appendChar:'\n'];
}
else if ([s length]-linebeg+(line-word)+
(([s length]==linebeg) ? [prefix length] : 1)<maxlen)
{
if ([s length]==linebeg)
[s appendSimpleString:prefix];
else
[s appendChar:' '];
[s appendString:word length:(line-word)];
linebeg=[s appendChar:'\n'];
}
else while(word<line)
{
for(tmp=word;(tmp<line)&&!((*tmp==' ')||(*tmp=='\t'));tmp++);
if (!isascii(*tmp)) tmp++;
if ([s length]-linebeg+(tmp-word)+1>=maxlen)
linebeg=[s appendChar:'\n'];
if ([s length]==linebeg)
[s appendSimpleString:prefix];
else
[s appendChar:' '];
for(;word<tmp;word++) [s appendChar:*word];
for(;(word<line)&&((*word==' ')||(*word=='\t'));word++);
}
if (line<end) line++;
if ((line<end)&&((*line=='\n')||(*line=='\r'))&&(*line!=*(line-1))) line++;
}
if (linebeg!=[s length]) [s appendChar:'\n'];
if (rx) free(rx);
if (prefix!=nil) [prefix free];
return s;
}
static int remove_roman_cell_droppings(char *buf, int len)
{
/* Evil Hack(tm). Removes the droppings left around in the ASCII stream
where the Text contained an embedded Cell. This blindly removes
_all_ occurences of this character, even when legitimately used !
(so use this only when specifically requested by the user.) */
const char DROPPING = '¬';
register char *s = buf, c, *d = buf + len, *end = d;
while (s < end)
{
if (*s++ == DROPPING)
{
d = s - 1;
while (s < end)
{
if ((c = *s++) != DROPPING) *d++ = c;
}
break;
}
}
return (int)(d - buf);
}
///#else /* KANJI */
#define iskanji(c) ((c) & 0x80)
#define BPMC 2 // byte per multibyte char
#define KINSOKU_ON
#ifndef KINSOKU_ON
#define iskinsoku(c) (0)
#else /* KINSOKU_ON */
static int iskinsoku(const char *firstbyte)
{
int i;
const char kinsoku_char[][BPMC+1]=
{
{0xa1,0xa2,'\0'}, // comma (1)
{0xa1,0xa3,'\0'}, // small circle
{0xa1,0xa4,'\0'}, // comma (2)
{0xa1,0xa5,'\0'}, // period
{0xa1,0xa9,'\0'}, // question mark
{0xa1,0xaa,'\0'}, // exclamation mark
{0xa1,0xca,'\0'}, // round blacket (left)
{0xa1,0xcb,'\0'}, // round blacket (right)
{0xa1,0xd6,'\0'}, // angular blacket (left)
{0xa1,0xd7,'\0'}, // anguler blacket (right)
};
const int ntypes=sizeof(kinsoku_char)/(BPMC+1);
if (*firstbyte=='.'||*firstbyte=='?'||*firstbyte=='!')
return 1;
for (i=0;i<ntypes;i++)
if (strncmp(firstbyte,kinsoku_char[i],BPMC)==0)
return 1;
return 0;
}
#endif /* KINSOKU_ON */
static int iseow(const char *d)
{
static char *oldd;
static int topoftext=1,secondbyte=0;
static int retval=0,oretval=0;
if (d==0)
{
topoftext=1;
secondbyte=0;
retval=oretval=0;
return retval;
}
if (!iskanji(*d))
{
if ((*d==' ')||(*d=='\t')) retval=1;
else retval=0;
}
else
{
if (secondbyte)
{
if (oldd==d)
retval=oretval;
else
{
secondbyte=0;
if (iskinsoku(d+1)) retval=0;
else retval=1;
}
}
else
{
if (oldd==d)
retval=oretval;
else
secondbyte=1,retval=0;
if (topoftext)
if (oldd!=d) topoftext=0;
}
}
oldd=(char *)d;
oretval=retval;
return retval;
}
static int hasperiod(const char *lineend, int linelen)
{
char *tmp=(char *)lineend;
int i;
const char mbperiod[][BPMC+1]=
{ // multi-byte characters of
{0xa1,0xa3,'\0'}, // small circle
{0xa1,0xa5,'\0'}, // period
{0xa1,0xa9,'\0'}, // question mark
{0xa1,0xaa,'\0'}, // exclamation mark
};
const int ntypes=sizeof(mbperiod)/(BPMC+1);
while (lineend-tmp<linelen)
{
if (isspace(*tmp)&&!iskanji(*tmp)) tmp--;
else
{
if (!iskanji(*tmp))
{
if (*tmp=='.'||*tmp=='?'||*tmp=='!')
return 1;
else
return 0;
}
for (i=0;i<ntypes;i++)
if (strncmp(tmp-1,mbperiod[i],BPMC)==0)
return 1;
return 0;
}
}
return 0;
}
static id quote_kanji_text(const char *text,int length,id mes)
{
id s=[[SimpleString alloc] init],prefix=nil;
int linebeg=0,i,shortline=0;
int maxlen=[MailMessage lineLength];
const char *line,*end,*word,*tmp,*prebeg,*preend;
regexp *rx=regcomp(EnhanceQuoteRegex);
char *lasteow;
if (!rx) return s;
prebeg=preend=line=text;
end=line+length;
iseow(0); tmp=0;
while(line<end)
{
if (!regexec(rx,line)) for(i=0;i<NSUBEXP;i++) rx->startp[i]=rx->endp[i]=line;
word=rx->endp[0];
if ((prefix==nil) ||
((word-line)!=(preend-prebeg)) ||
(strncmp(line,prebeg,preend-prebeg)!=0))
{
prebeg=line;
preend=word;
if (prefix!=nil) prefix=[prefix free];
prefix=interpret_escapes(EnhanceQuotePrefix, mes, rx);
if (linebeg!=[s length]) linebeg=[s appendChar:'\n'];
}
while((word<end) && iseow(word)) word++;
for(line=word;(line<end)&&(*line!='\n')&&(*line!='\r');line++);
if ((*word=='\n')||(*word=='\r'))
{
if (linebeg!=[s length]) [s appendChar:'\n'];
[s appendSimpleString:prefix];
linebeg=[s appendChar:'\n'];
iseow(0);
}
else if ([s length]-linebeg+(line-word)+
(([s length]==linebeg) ? [prefix length] : 1)<maxlen)
{
if ([s length]==linebeg)
[s appendSimpleString:prefix];
else
if (!iskanji(*(word-[prefix length]))) [s appendChar:' '];
[s appendString:word length:(line-word)];
linebeg=[s appendChar:'\n'];
iseow(0);
}
else
{
if (line-word+[prefix length]>maxlen) shortline=0;
else shortline=1;
while(word<line)
{
lasteow=(char *)tmp-1;
for(tmp=word;(tmp<line)&&!iseow(tmp);tmp++);
if ([s length]-linebeg+(tmp-word)+1>=maxlen)
{
if (tmp-word>maxlen)
{
if ([s length]==linebeg) [s appendSimpleString:prefix];
do
{
[s appendChar:*word];
word++;
} while([s length]-linebeg<maxlen);
}
linebeg=[s appendChar:'\n'];
}
if ([s length]==linebeg)
[s appendSimpleString:prefix];
else
if (!iskanji(*lasteow)||!iskanji(*word))
[s appendChar:' '];
for(;word<tmp;word++) [s appendChar:*word];
if (iskanji(*tmp)) [s appendChar:*tmp];
for(;(word<line)&&iseow(word);word++);
}
if ((shortline||hasperiod(tmp,maxlen)) && ([s length]!=linebeg))
linebeg=[s appendChar:'\n'];
iseow(0);
}
if (line<end) line++;
if ((line<end)&&((*line=='\n')||(*line=='\r'))&&(*line!=*(line-1))) line++;
}
if (linebeg!=[s length]) [s appendChar:'\n'];
if (rx) free(rx);
if (prefix!=nil) [prefix free];
return s;
}
static int remove_kanji_cell_droppings(char *buf, int len)
{
// No-op for now.
return len;
}
///#endif /* KANJI */
static id quote_text(const char *text,int length,id mes)
{
return EnhanceUseKanji() ? quote_kanji_text(text,length,mes) : quote_roman_text(text,length,mes);
}
static int remove_cell_droppings(char *buf, int len)
{
return EnhanceUseKanji() ? remove_kanji_cell_droppings(buf, len) : remove_roman_cell_droppings(buf, len);
}
@implementation EnhanceSend
+ finishLoading:(struct mach_header *)header
{
[self poseAs:[self superclass]];
SignatureHash=[[HashTable alloc] initKeyDesc:"@" valueDesc:"i"];
QuotationHash=[[HashTable alloc] initKeyDesc:"@" valueDesc:"i"];
ReplyBoxHash=[[HashTable alloc] initKeyDesc:"@" valueDesc:"%"];
InReplyToHash=[[HashTable alloc] initKeyDesc:"@" valueDesc:"%"];
return self;
}
+ startUnloading
{
SignatureHash=[SignatureHash free];
QuotationHash=[QuotationHash free];
ReplyBoxHash=[ReplyBoxHash free];
InReplyToHash=[InReplyToHash free];
return self;
}
// Access methods.
- (BOOL)signature { return (int)[SignatureHash valueForKey:self]; }
- (void)setSignature:(BOOL)flag { [SignatureHash insertKey:self value:(void*)(int)flag]; }
- (BOOL)quotation { return (int)[QuotationHash valueForKey:self]; }
- (void)setQuotation:(BOOL)flag { [QuotationHash insertKey:self value:(void*)(int)flag]; }
- (NXAtom)replyBox { return (NXAtom)[ReplyBoxHash valueForKey:self]; }
- (void)setReplyBox:(const char *)val
{
if (val)
[ReplyBoxHash insertKey:self value:(void *)NXUniqueString(val)];
else
[ReplyBoxHash removeKey:self];
}
- (NXAtom)inReplyTo { return (NXAtom)[InReplyToHash valueForKey:self]; }
- (void)setInReplyTo:(const char *)val
{
if (val)
[InReplyToHash insertKey:self value:(void *)NXUniqueString(val)];
else
[InReplyToHash removeKey:self];
}
// Support: perform selector while suppressing visual artefacts in text.
- performBatchingDisplay:(SEL)aSelector with:arg
{
id ret;
Window *w = [self window];
BOOL isDisplayEnabled = [w isDisplayEnabled];
if (isDisplayEnabled) [w disableDisplay];
ret = [self perform:aSelector with:arg];
if (isDisplayEnabled)
{
[w reenableDisplay];
[w displayIfNeeded];
/// [w flushWindow];
}
return ret;
}
- performBatchingDisplay:(SEL)aSelector
{
return [self performBatchingDisplay:aSelector with:nil];
}
// Semi-private support methods for reading signature.
enum { SIG_PLAIN, SIG_RTF, SIG_RTFD };
- (BOOL)_readSignatureFromDir:(const char *)dir type:(int)type
{
static const char *const ext[] = { "", ".rtf", ".rtfd" };
char path[MAXPATHLEN+1];
sprintf(path, "%s/%s%s", dir, EnhanceSignatureFilename, ext[type]);
switch (type)
{
case SIG_PLAIN:
return ([text openFrom:path] != nil);
case SIG_RTF:
return ([text openRichFrom:path] != nil);
case SIG_RTFD:
return ([text openRTFDFrom:path] == NX_RTFDErrorNone);
}
return NO;
}
- (BOOL)_readSignatureFromDir:(const char *)dir
{
int type;
if (!dir) return NO;
if (sendFormat==0)
{
// prefer plainest signature.
for (type = SIG_PLAIN; type <= SIG_RTFD; type++)
{
if ([self _readSignatureFromDir:dir type:type]) return YES;
}
}
else
{
// prefer richest signature.
for (type = SIG_RTFD; type >= SIG_PLAIN; type--)
{
if ([self _readSignatureFromDir:dir type:type]) return YES;
}
}
return NO;
}
- (void)_normalizeSelection
{
[text setSelGray:NX_BLACK];
if (sendFormat!=0) [text setSelFont:[Text getDefaultFont]];
}
- (void)_retoucheSignature
{
int end = [text textLength];
// Make sure .signature ends with newline.
if (end > 0)
{
char c;
[text getSubstring:&c start:end-1 length:1];
if (c != '\n')
{
[text setSel:end:end];
[text replaceSel:"\n"];
end += 1;
}
// Avoid bleeding of .signature text properties in following text.
[text setSel:end-1:end];
[self _normalizeSelection];
}
// Avoid bleeding of .signature text properties in preceding text.
[text setSel:0:0];
[self _normalizeSelection];
[text replaceSel:EnhanceSignatureSeparator];
[text setSel:0:0];
[text replaceSel:"\n"];
}
- (void)doSignature
{
if (!editing && EnhanceSignatureFilename && [self signature])
{
id oreader=[self getMailbox];
id obox=oreader ? [oreader mailbox] : nil;
const char *box=obox ? [obox dirname] : 0;
const char *home=NXHomeDirectory();
if ([self _readSignatureFromDir:box] ||
[self _readSignatureFromDir:home])
{
[self _retoucheSignature];
}
if (sendFormat==0) [self makeAscii:self];
[self updateSpecialDelivery];
[self updateSize:self];
[text update];
// Kludge; really should try harder to restore field/cursor pos.
[text setSel:0:0];
}
}
- (void)doQuote
{
if (!editing && [self quotation])
{
id oreader=[self getMailbox];
id orig=oreader ? [oreader mailMessage] : nil;
id otext=oreader ? [oreader text] : nil;
id s;
int start,len;
NXSelPt beg,end;
char *buf,*c;
const int max_bpmc=(EnhanceUseKanji() ? 3 : 1); // maximum bytes per multibyte char
if ((orig==nil) || (otext==nil)) return;
if (!editing && !(EnhanceSignatureFilename && [self signature]))
{
// start from clean slate, but only if signature is not present.
[text setText:""];
}
s=[[SimpleString alloc] init];
if (EnhanceQuoteIntro)
{
[s appendAndFreeSimpleString:interpret_escapes(EnhanceQuoteIntro,orig,0)];
[s appendChar:'\n'];
}
[otext getSel:&beg:&end];
if (beg.cp!=end.cp)
{
start=beg.cp;
len=end.cp-beg.cp;
c=buf=NXZoneMalloc(EnhanceScratchZone(), len*max_bpmc+1);
len=[otext getSubstring:buf start:start length:len];
}
else
{
start=0;
len=[otext byteLength];
buf=NXZoneMalloc(EnhanceScratchZone(),len+1);
[otext getSubstring:buf start:start length:len];
// Skip header.
for(c=buf;c<buf+len-1;c++) if ((c[0]=='\n') && (c[1]=='\n')) break;
while ((c<buf+len) && (*c=='\n')) c++;
len-=(c-buf);
}
if (EnhanceRemoveCellDroppings) len = remove_cell_droppings(c, len);
start=[s textLength];
[s appendAndFreeSimpleString:quote_text(c,len,orig)];
len=[s textLength]-start;
[s appendChar:'\n']; // Make room for reply.
[text setSel:0:0];
[text replaceSel:[s string]];
if ((sendFormat!=0) && EnhanceQuoteColoring)
{
[text setSel:start:start+len];
[text setSelColor:EnhanceQuoteColor];
}
if (sendFormat==0) [self makeAscii:self];
[self updateSpecialDelivery];
[self updateSize:self];
[text update];
[text setSel:start+len+1:start+len+1]; // move cursor to beyond quoted text.
[text scrollSelToVisible];
free(buf);
[s free];
}
}
- (void)doQuoteSig
{
if (!editing)
{
[self doSignature];
[self doQuote];
}
}
- initSendWindow:fp16
{
id ret=[super initSendWindow:fp16];
[self setSignature:(EnhanceInsertSignature^EnhanceControlP())];
[self setQuotation:NO];
[self setReplyBox:NULL];
[self setInReplyTo:NULL];
// Kludge to make size field work with display batching.
if (sizeField) [sizeField setBackgroundGray:NX_LTGRAY];
[self performBatchingDisplay:@selector(doSignature)];
[form selectTextAt:0];
return ret;
}
- free
{
[SignatureHash removeKey:self];
[QuotationHash removeKey:self];
[ReplyBoxHash removeKey:self];
[InReplyToHash removeKey:self];
return [super free];
}
- deliver:button
{
id ret, def;
NXAtom replybox, origbox;
// XXX modifiers overloaded with regular archiving controls.
// XXX allow use of both archive (alt) and unarchive (ctrl) until we
// can come up with a better solution.
if (EnhanceAutoSpellCheck &~ (EnhanceAlternateP() | EnhanceControlP()))
{
id spell=[NXSpellChecker sharedInstance];
id panel=(spell!=nil) ? [spell spellingPanel] : nil;
if ((spell!=nil)
&&[spell checkSpelling:NX_CheckSpellingFromStart of:self])
{
if (panel!=nil) [panel orderFront:self];
return nil;
}
else
{
if (panel!=nil) [panel orderOut:self];
}
}
def=[Defaults new];
origbox=NXUniqueString([def outgoingMailbox]);
replybox=[self replyBox];
if (EnhanceRepliesWithOriginals && replybox)
[def setOutgoingMailbox:replybox];
ret=[super deliver:button];
if (EnhanceRepliesWithOriginals && replybox)
[def setOutgoingMailbox:origbox];
return ret;
}
- mailMessage
{
id mes=[super mailMessage];
id ximage;
char buf[1024];
const char *tmp;
sprintf(buf, "%s %s (Enhance %s)", [NXApp appName], [NXApp appVersion], EnhanceVersion);
[mes setHeaderKey:"X-Nextstep-Mailer" value:buf];
if (tmp=[self inReplyTo])
{
[mes setHeaderKey:"In-Reply-To" value:tmp];
}
if (EnhanceInsertXFace && EnhanceXFace && *EnhanceXFace
&& (ximage=[[NXImage alloc] initFromFile:EnhanceXFace]))
{
char *tmp=[ximage xFace];
ximage=[ximage free];
if (tmp)
{
[mes setHeaderKey:"X-Face" value:tmp];
free(tmp);
}
}
if (EnhanceInsertXImageURL && EnhanceXImageURL && *EnhanceXImageURL &&
EnhanceXImageURL[strcspn(EnhanceXImageURL, " \n\t\r")] == '\0')
{
[mes setHeaderKey:"X-Image-URL" value:EnhanceXImageURL];
}
return mes;
}
- (void)_enhanceDefaultsChanged
{
[super defaultsChanged];
[self doQuoteSig]; // Sig defaults may have changed.
}
- (void)defaultsChanged
{
[self performBatchingDisplay:@selector(_enhanceDefaultsChanged)];
}
- (void)_enhanceSetSendFormat:(int)arg
{
[super setSendFormat:arg];
[self doQuoteSig]; // Sig is dependent on message format.
}
- (void)setSendFormat:(int)arg
{
[self performBatchingDisplay:@selector(_enhanceSetSendFormat:) with:(id)arg];
}
- restoreDraftFromMessage:message
{
#if 0
if (EnhanceRestoreDeletesDraft)
{
[toc setState:'d' forMsg:number flush:YES];
}
#endif
// Suppress signature quoting.
[self setSignature:NO];
[text setText:""];
[self updateSpecialDelivery];
return [super restoreDraftFromMessage:message];
}
- _enhanceReplyHelper:(BOOL)flag
{
id oreader=[self getMailbox];
id obox=oreader ? [oreader mailbox] : nil;
id orig=oreader ? [oreader mailMessage] : nil;
int wasEditing = editing;
id ret=[super replyHelper:flag];
// 3.3 replyHelper leaves editing flag alone, but 4.x changes it.
// For quoting/signature to work, editing must be off the first time.
if (editing && !wasEditing)
{
editing = NO;
[[self window] setDocEdited:NO];
}
if ((orig != nil) && EnhanceFlagReplies) [oreader setFlagged:YES];
[self setReplyBox:[obox dirname]];
[self setInReplyTo:[orig headerValueForKey:"Message-Id"]];
if (!editing)
{
BOOL wasQuoting = [self quotation];
BOOL willQuote = ((wasQuoting | EnhanceQuoteReplies) ^ EnhanceControlP());
if (wasQuoting != willQuote)
{
[self setQuotation:willQuote];
[self doQuoteSig];
}
}
return ret;
}
- replyHelper:(BOOL)flag
{
return [self performBatchingDisplay:@selector(_enhanceReplyHelper:) with:(id)(int)flag];
}
// <NXReadonlyTextStream> methods. (NXSpellChecker delegate)
- openTextStream
{
[text openTextStream];
return self;
}
- (BOOL)seekToCharacterAt:(int)offset relativeTo:(int)seekMode
{
return [text seekToCharacterAt:offset relativeTo:seekMode];
}
- (int)readCharacters:(char *)buffer count:(int)count
{
return [text readCharacters:buffer count:count];
}
- (int)currentCharacterOffset
{
return [text currentCharacterOffset];
}
- (BOOL)isAtEOTS
{
return [text isAtEOTS];
}
- closeTextStream
{
[text closeTextStream];
return self;
}
// <NXSelectText> methods. (NXSpellChecker delegate)
- (void)selectCharactersFrom:(int)start to:(int)end
{
[text selectCharactersFrom:start to:end];
}
- (int)selectionCharacterCount
{
return [text selectionCharacterCount];
}
- (int)readCharactersFromSelection:(char *)buffer count:(int)count
{
return [text readCharactersFromSelection:buffer count:count];
}
- (void)makeSelectionVisible
{
[text makeSelectionVisible];
}
// <NXChangeSpelling> methods.
- changeSpelling:sender
{
return ([text changeSpelling:sender]==nil) ? nil : self;
}
// <NXIgnoreMisspelledWords> methods.
- (int)spellDocumentTag
{
return [text spellDocumentTag];
}
@end // EnhanceSend
@implementation MediaText(EnhanceSend)
/* Support functions: open a stream for reading a named file.
This used to try NXMapFile first, then if that doesn't work try NXOpenFile.
But since that exercise was meant to allow reading from a fifo, and
NXMapFile silently succeeds opening the fifo (just doesn't return any
data) just use NXOpenFile. -- it's purportedly less efficient but for now
we're only using it to read signature files, so efficiency is not really
important :-)
(we have to pass a file descriptor back to the caller, since NXStream
has no (public) way of determining how it was opened,
and closing it is dependent on what type it was -- bad abstraction:-|) */
static NXStream *openStreamForReading(const char *file, int *fdP)
{
int fd = -1;
NXStream *s = NULL;
#if 0
NXStream *s = NXMapFile(file, NX_READONLY);
if (s == NULL && errno != ENOENT && fdP)
#endif
{
if ((fd = open(file, O_RDONLY)) >= 0)
{
if ((s = NXOpenFile(fd, NX_READONLY)) == NULL)
{
close(fd);
fd = -1;
}
}
}
if (fdP) *fdP = fd;
return s;
}
static void closeStream(NXStream *s, int fd)
{
if (s)
{
if (fd >= 0)
{
NXClose(s);
}
else
{
NXCloseMemory(s, NX_FREEBUFFER);
}
}
if (fd >= 0) close(fd);
}
- openFrom:(const char *)file
{
id ret = nil;
int fd;
NXStream *s = openStreamForReading(file, &fd);
if (s)
{
ret = [self readText:s];
closeStream(s, fd);
}
return ret;
}
- openRichFrom:(const char *)file
{
id ret = nil;
int fd;
NXStream *s = openStreamForReading(file, &fd);
if (s)
{
ret = [self readRichText:s];
closeStream(s, fd);
}
return ret;
}
@end // MediaText (EnhanceSend)
// Overrule SendDemo to kludge around rich signature weirdness.
@interface EnhanceSendDemo: SendDemo
- (int)openSend:(int *)aWindow;
@end
@implementation EnhanceSendDemo
+ finishLoading:(struct mach_header *)header
{
[self poseAs:[self superclass]];
[[SendDemo new] changeClassTo:[self class]];
return self;
}
- (int)openSend:(int *)aWindow
{
int ret;
int oInsertSignature = EnhanceInsertSignature;
EnhanceInsertSignature = NO;
ret = [super openSend:aWindow];
EnhanceInsertSignature = oInsertSignature;
return ret;
}
@end // EnhanceSendDemo
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.