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.