├── interfac ├── error.h ├── isoconv.h ├── tagline.h ├── mysystem.h ├── main.cc ├── isoconv.cc ├── mmcolor.h ├── help.cc ├── letterl.cc ├── tagline.cc ├── mmcolor.cc ├── addrbook.cc ├── packet.cc ├── arealist.cc └── mysystem.cc ├── tclist ├── mmail ├── compress.h ├── opx.h ├── misc.h ├── omen.h ├── soup.h ├── netadd.cc ├── resource.h ├── bw.h ├── qwk.h ├── compress.cc ├── pktbase.h ├── mmail.cc ├── read.cc ├── filelist.cc └── area.cc ├── Makefile.vc ├── Makefile.bcc ├── Makefile.wcc ├── colors ├── aqua.col ├── ingo.col ├── ver01.col ├── gilmore.col ├── tradit.col ├── tonys.col ├── holger2.col ├── toutant.col ├── tuukka.col └── COLORS.md ├── README.md ├── Makefile ├── INSTALL.md ├── config.h └── depend /interfac/error.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * error-reporting class 4 | 5 | Copyright 1998-2018 William McBrine 6 | Distributed under the GNU General Public License, version 3 or later. */ 7 | 8 | class ErrorType 9 | { 10 | char *origdir; 11 | public: 12 | ErrorType(); 13 | ~ErrorType(); 14 | const char *getOrigDir(); 15 | }; 16 | 17 | extern ErrorType error; 18 | -------------------------------------------------------------------------------- /tclist: -------------------------------------------------------------------------------- 1 | mysystem.obj 2 | mmcolor.obj 3 | isoconv.obj 4 | basic.obj 5 | interfac.obj 6 | packet.obj 7 | arealist.obj 8 | letterl.obj 9 | letterw.obj 10 | -Yo lettpost.obj 11 | ansiview.obj 12 | addrbook.obj 13 | tagline.obj 14 | -Yo- help.obj 15 | main.obj 16 | misc.obj 17 | resource.obj 18 | mmail.obj 19 | filelist.obj 20 | netadd.obj 21 | area.obj 22 | letter.obj 23 | read.obj 24 | compress.obj 25 | pktbase.obj 26 | -Yo bw.obj 27 | qwk.obj 28 | omen.obj 29 | soup.obj 30 | opx.obj 31 | -------------------------------------------------------------------------------- /mmail/compress.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * compress and decompress packets 4 | 5 | Copyright 1997 John Zero 6 | Copyright 1998-2021 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #ifndef COMPRESS_H 10 | #define COMPRESS_H 11 | 12 | #include "mmail.h" 13 | 14 | pktstatus uncompressFile(const char *, const char *, bool = false); 15 | int compressAddFile(const char *, const char *, const char *); 16 | const char *defExtent(); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /interfac/isoconv.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * conversion tables ISO 8859-1 <-> IBM codepage 437 4 | 5 | Copyright 1997 Peter Krefting , 6 | Toth Istvan 7 | Copyright 1998-2017 William McBrine 8 | Distributed under the GNU General Public License, version 3 or later. */ 9 | 10 | #ifndef CONVTAB_H 11 | #define CONVTAB_H 12 | 13 | extern bool isoConsole; // ISO8859-1 <--> CP437 (DOS) conversion? 14 | 15 | extern const char *dos2isotab; 16 | extern const char *iso2dostab; 17 | 18 | char *charconv_in(char *); 19 | char *charconv_out(char *); 20 | char *letterconv_in(char *); 21 | char *letterconv_out(char *); 22 | char *areaconv_in(char *); 23 | char *areaconv_out(char *); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /Makefile.vc: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------- 2 | # MultiMail Makefile (top) for Microsoft Visual C++ 3 | #-------------------------------------------------- 4 | 5 | CURS_DIR = \pdcurses 6 | LIBS = $(CURS_DIR)\wincon\pdcurses.lib user32.lib advapi32.lib 7 | COMPILER = $(CC) -nologo -c -O2 -D__WIN32__ 8 | 9 | all: mm 10 | 11 | O = obj 12 | 13 | .SUFFIXES: .cc 14 | 15 | {mmail}.cc.obj:: 16 | $(COMPILER) $< 17 | 18 | {interfac}.cc.obj:: 19 | $(COMPILER) -I$(CURS_DIR) $< 20 | 21 | MOBJS = misc.$(O) resource.$(O) mmail.$(O) filelist.$(O) netadd.$(O) \ 22 | area.$(O) letter.$(O) read.$(O) compress.$(O) pktbase.$(O) bw.$(O) \ 23 | qwk.$(O) omen.$(O) soup.$(O) opx.$(O) 24 | 25 | IOBJS = mmcolor.$(O) mysystem.$(O) isoconv.$(O) basic.$(O) interfac.$(O) \ 26 | packet.$(O) arealist.$(O) letterl.$(O) letterw.$(O) lettpost.$(O) \ 27 | ansiview.$(O) addrbook.$(O) tagline.$(O) help.$(O) main.$(O) 28 | 29 | mm: $(MOBJS) $(IOBJS) 30 | link -out:mm.exe *.obj $(LIBS) 31 | 32 | clean: 33 | del *.obj 34 | del mm.exe 35 | 36 | !include depend 37 | -------------------------------------------------------------------------------- /Makefile.bcc: -------------------------------------------------------------------------------- 1 | #----------------------------------------------- 2 | # MultiMail Makefile (top) for Borland/Turbo C++ 3 | #----------------------------------------------- 4 | 5 | !ifndef CURS_DIR 6 | CURS_DIR = \pdcurses 7 | !endif 8 | 9 | !if $(SYS) == DOS 10 | LIBS = spawnl.lib $(CURS_DIR)\dos\pdcurses.lib 11 | !else 12 | LIBS = $(CURS_DIR)\wincon\pdcurses.lib 13 | CC = bcc32c -q -D__WIN32__ 14 | !endif 15 | 16 | LIST = tclist 17 | COMPILER = $(CC) -c -P 18 | 19 | O = obj 20 | 21 | OPTS = -Immail -Iinterfac 22 | 23 | .SUFFIXES: .cc 24 | 25 | .autodepend 26 | 27 | {mmail}.cc.obj: 28 | $(COMPILER) $(OPTS) $< 29 | 30 | {interfac}.cc.obj: 31 | $(COMPILER) $(OPTS) -I$(CURS_DIR) $< 32 | 33 | MOBJS = misc.$(O) resource.$(O) mmail.$(O) filelist.$(O) netadd.$(O) \ 34 | area.$(O) letter.$(O) read.$(O) compress.$(O) pktbase.$(O) bw.$(O) \ 35 | qwk.$(O) omen.$(O) soup.$(O) opx.$(O) 36 | 37 | IOBJS = mmcolor.$(O) mysystem.$(O) isoconv.$(O) basic.$(O) interfac.$(O) \ 38 | packet.$(O) arealist.$(O) letterl.$(O) letterw.$(O) lettpost.$(O) \ 39 | ansiview.$(O) addrbook.$(O) tagline.$(O) help.$(O) main.$(O) 40 | 41 | all: mm 42 | 43 | mm: $(MOBJS) $(IOBJS) 44 | $(CC) -emm @$(LIST) $(LIBS) 45 | 46 | clean: 47 | del *.obj 48 | del mm.exe 49 | del mm.tds 50 | -------------------------------------------------------------------------------- /interfac/tagline.h: -------------------------------------------------------------------------------- 1 | /* Perhaps these don't deserve to be enshrined in the source code, but I 2 | wanted to simplify installation by having the tagline file auto-generated 3 | for new users. You can replace this list with whatever taglines you like 4 | (just make sure the last item is a zero). - WJM3 */ 5 | 6 | #ifndef TAGLINES_H 7 | #define TAGLINES_H 8 | 9 | static const char *defaultTags[] = { 10 | "MultiMail, the new multi-platform, multi-format offline reader!", 11 | "\"42? 7 and a half million years and all you can come up with is 42?!\"", 12 | "2 + 2 = 5 for extremely large values of 2.", 13 | "Computer Hacker wanted. Must have own axe.", 14 | "DalekDOS v(overflow): (I)Obey (V)ision impaired (E)xterminate", 15 | "Direct from the Ministry of Silly Walks", 16 | "Gone crazy, be back later, please leave message.", 17 | "Got my tie caught in the fax... Suddenly I was in L.A.", 18 | "He does the work of 3 Men...Moe, Larry & Curly", 19 | "Heisenberg may have slept here.", 20 | "Internal Error: The system has been taken over by sheep at line 19960", 21 | "So easy, a child could do it. Child sold separately.", 22 | "The number you have dialed...Nine-one-one...has been changed.", 23 | "What is mind? No matter! What is matter? Never mind! - Homer S.", 24 | 0 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /mmail/opx.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * OPX 4 | 5 | Copyright 1999-2021 William McBrine 6 | Distributed under the GNU General Public License, version 3 or later. */ 7 | 8 | #ifndef OPX_H 9 | #define OPX_H 10 | 11 | #include "pktbase.h" 12 | #include "opxstrct.h" 13 | 14 | class opxpack : public pktbase 15 | { 16 | ocfgHeader confhead; 17 | char *bulletins; 18 | 19 | char *pstrget(void *); 20 | void readBrdinfoDat(); 21 | void buildIndices(); 22 | 23 | void getblk(int, long &, long, unsigned char *&, unsigned char *&); 24 | void endproc(letter_header &); 25 | public: 26 | opxpack(); 27 | ~opxpack(); 28 | area_header *getNextArea(); 29 | letter_header *getNextLetter(); 30 | ocfgHeader *offhead(); 31 | const char *oldFlagsName(); 32 | bool readOldFlags(); 33 | bool saveOldFlags(); 34 | }; 35 | 36 | class opxreply : public pktreply 37 | { 38 | class upl_opx : public upl_base { 39 | public: 40 | fidoHead rhead; 41 | net_address na; 42 | char *msgid; 43 | int area; 44 | 45 | upl_opx(const char * = 0); 46 | ~upl_opx(); 47 | }; 48 | 49 | int getArea(const char *); 50 | bool getRep1(const char *, upl_opx *); 51 | void getReplies(FILE *); 52 | const char *freeFileName(upl_opx *); 53 | void addRep1(FILE *, upl_base *, int); 54 | void addHeader(FILE *); 55 | void repFileName(); 56 | const char *repTemplate(bool); 57 | public: 58 | opxreply(); 59 | ~opxreply(); 60 | area_header *getNextArea(); 61 | letter_header *getNextLetter(); 62 | void enterLetter(letter_header &, const char *, long); 63 | bool getOffConfig(); 64 | bool makeOffConfig(); 65 | }; 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /mmail/misc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * miscellaneous routines (global) 4 | 5 | Copyright 1996-1997 Toth Istvan 6 | Copyright 1997-2020 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #ifndef MISC_H 10 | #define MISC_H 11 | 12 | extern "C" { 13 | #include 14 | #include 15 | } 16 | 17 | unsigned getshort(const unsigned char *); 18 | unsigned long getlong(const unsigned char *); 19 | unsigned long getblong(const unsigned char *); 20 | void putshort(unsigned char *, unsigned); 21 | void putlong(unsigned char *, unsigned long); 22 | void putblong(unsigned char *, unsigned long); 23 | 24 | struct tm *getdostime(unsigned long); 25 | unsigned long mkdostime(struct tm *); 26 | 27 | char *strnzcpy(char *, const char *, size_t); 28 | void cropesp(char *); 29 | void unspace(char *); 30 | char *strdupplus(const char *); 31 | char *strdupblank(const char *); 32 | char *fullpath(const char *, const char *); 33 | char *quotespace(const char *); 34 | const char *findBaseName(const char *); 35 | char *fixPath(const char *); 36 | int getNumExt(const char *); 37 | const char *stripre(const char *); 38 | const char *searchstr(const char *, const char *, int = -1); 39 | const char *fromAddr(const char *); 40 | const char *fromName(const char *); 41 | bool quoteIt(const char *); 42 | 43 | void headdec(const char *, const char *, char *); 44 | void headenc(const unsigned char *, const char *, FILE *); 45 | unsigned char *qpdecode(unsigned char *); 46 | long qpdecode(FILE *, FILE *); 47 | void qpencode(FILE *, FILE *); 48 | 49 | void fatalError(const char *); // actually in ../interfac/main.cc! 50 | void pauseError(const char *); // actually in ../interfac/main.cc! 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /Makefile.wcc: -------------------------------------------------------------------------------- 1 | #----------------------------------------------- 2 | # MultiMail Makefile (top) for Open Watcom C++ 3 | #----------------------------------------------- 4 | 5 | # For Windows: 6 | 7 | CURS_DIR = /pdcurses 8 | 9 | LIBS = $(CURS_DIR)/wincon/pdcurses.lib 10 | COMPILER = wpp386 -zq -bt=nt -D__WIN32__ -DWIN32 11 | LINKER = wlink system nt 12 | 13 | #-------------------------------------------------------------- 14 | # For 32-bit OS/2: 15 | 16 | !ifeq SYS OS2 17 | LIBS = $(CURS_DIR)/os2/pdcurses.lib 18 | COMPILER = wpp386 -zq -bt=os2v2 -D__OS2__ 19 | LINKER = wlink system os2v2 20 | !endif 21 | 22 | #-------------------------------------------------------------- 23 | # For 32-bit DOS: 24 | 25 | !ifeq SYS DOS32 26 | LIBS = $(CURS_DIR)/dos/pdcurses.lib 27 | COMPILER = wpp386 -zq -bt=dos4g -mf -D__MSDOS__ 28 | LINKER = wlink system dos4g 29 | !endif 30 | 31 | #-------------------------------------------------------------- 32 | # For 16-bit DOS: 33 | 34 | !ifeq SYS DOS16 35 | LIBS = $(CURS_DIR)/dos/pdcurses.lib 36 | COMPILER = wpp -zq -bt=dos -ml -D__MSDOS__ 37 | LINKER = wlink system dos 38 | !endif 39 | 40 | !ifdef __LOADDLL__ 41 | ! loaddll wlink wlinkd 42 | ! loaddll wlib wlibd 43 | ! loaddll wpp wppdi86 44 | ! loaddll wpp386 wppd386 45 | !endif 46 | 47 | O = obj 48 | 49 | CPPFLAGS = -I$(CURS_DIR) 50 | 51 | .cc: mmail;interfac 52 | .cc.obj: .autodepend 53 | $(COMPILER) $(CPPFLAGS) $< 54 | 55 | MOBJS = misc.$(O) resource.$(O) mmail.$(O) filelist.$(O) netadd.$(O) & 56 | area.$(O) letter.$(O) read.$(O) compress.$(O) pktbase.$(O) bw.$(O) & 57 | qwk.$(O) omen.$(O) soup.$(O) opx.$(O) 58 | 59 | IOBJS = mmcolor.$(O) mysystem.$(O) isoconv.$(O) basic.$(O) interfac.$(O) & 60 | packet.$(O) arealist.$(O) letterl.$(O) letterw.$(O) lettpost.$(O) & 61 | ansiview.$(O) addrbook.$(O) tagline.$(O) help.$(O) main.$(O) 62 | 63 | all: mm.exe 64 | 65 | mm.exe: $(MOBJS) $(IOBJS) 66 | $(LINKER) name mm.exe file *.obj libfile $(LIBS) 67 | 68 | clean 69 | del *.obj 70 | del mm.exe 71 | -------------------------------------------------------------------------------- /mmail/omen.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * OMEN 4 | 5 | Copyright 1999-2021 William McBrine 6 | Distributed under the GNU General Public License, version 3 or later. */ 7 | 8 | #ifndef OMEN_H 9 | #define OMEN_H 10 | 11 | #include "pktbase.h" 12 | 13 | // HEADERxy.BBS records: 14 | struct omenReplyRec { 15 | unsigned char command; 16 | unsigned char curboard, moveboard; 17 | unsigned char msgnumber[2]; 18 | 19 | unsigned char tolen; 20 | char to[35]; 21 | 22 | unsigned char sublen; 23 | char subject[72]; 24 | 25 | unsigned char destzone[2], destnet[2], destnode[2]; 26 | 27 | unsigned char netattrib; 28 | 29 | unsigned char aliaslen; 30 | char alias[20]; 31 | 32 | unsigned char curhighboard; 33 | unsigned char movehighboard; 34 | unsigned char msghighnumber[2]; 35 | char extraspace[4]; 36 | }; 37 | 38 | class omen : public pktbase 39 | { 40 | char extent[4]; 41 | unsigned useLatin; 42 | 43 | void readSystemBBS(); 44 | void buildIndices(); 45 | 46 | void prefirstblk(); 47 | public: 48 | omen(); 49 | ~omen(); 50 | area_header *getNextArea(); 51 | letter_header *getNextLetter(); 52 | const char *getExtent(); 53 | bool isLatin(); 54 | }; 55 | 56 | class omenrep : public pktreply 57 | { 58 | class upl_omen : public upl_base 59 | { 60 | omenReplyRec omen_rec; 61 | public: 62 | char subject[73], to[36]; 63 | net_address na; 64 | long refnum; 65 | int origArea; 66 | bool privat; 67 | 68 | upl_omen(const char * = 0); 69 | 70 | bool init(FILE *); 71 | void output(FILE *); 72 | }; 73 | bool getRep1(FILE *, upl_omen *, int); 74 | void getReplies(FILE *); 75 | void addRep1(FILE *, upl_base *, int); 76 | void addHeader(FILE *); 77 | void repFileName(); 78 | const char *repTemplate(bool); 79 | public: 80 | omenrep(); 81 | ~omenrep(); 82 | area_header *getNextArea(); 83 | letter_header *getNextLetter(); 84 | void enterLetter(letter_header &, const char *, long); 85 | bool getOffConfig(); 86 | bool makeOffConfig(); 87 | }; 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /interfac/mysystem.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * protos for mysystem.cc 4 | 5 | Copyright 1997-2017 William McBrine 6 | Distributed under the GNU General Public License, version 3 or later. */ 7 | 8 | #ifndef MYSYSTEM_H 9 | #define MYSYSTEM_H 10 | 11 | extern "C" { 12 | #include 13 | } 14 | 15 | class mystat; 16 | 17 | char *myfgets(char *, int, FILE *); 18 | int mysystem(const char *); 19 | int mysystem2(const char *, const char *); 20 | char *mytmpdir(const char *); 21 | char *mytmpnam(); 22 | void edit(const char *); 23 | int mychdir(const char *); 24 | int mymkdir(const char *); 25 | void myrmdir(const char *); 26 | char *mygetcwd(); 27 | const char *sysname(); 28 | bool myopendir(const char *); 29 | const char *myreaddir(mystat &); 30 | void clearDirectory(const char *); 31 | time_t touchFile(const char *); 32 | 33 | #ifdef LIMIT_MEM 34 | long maxfreemem(); 35 | long limitmem(long); 36 | #else 37 | # define limitmem(x) x 38 | #endif 39 | 40 | char *canonize(char *); 41 | 42 | #ifdef HAS_HOME 43 | const char *homify(const char *); 44 | #else 45 | # define homify(x) x 46 | #endif 47 | 48 | #ifdef USE_SHELL 49 | class Shell 50 | { 51 | char *prompt; 52 | public: 53 | Shell(); 54 | ~Shell(); 55 | void out(); 56 | }; 57 | #endif 58 | 59 | #ifdef EXTRAPATH 60 | class ExtraPath 61 | { 62 | char *newpath; 63 | public: 64 | ExtraPath(); 65 | ~ExtraPath(); 66 | }; 67 | #endif 68 | 69 | class mystat 70 | { 71 | int mode; 72 | off_t size; 73 | time_t date; 74 | public: 75 | mystat(const char *); 76 | mystat(); 77 | 78 | bool init(const char *); 79 | #ifdef USE_FINDFIRST 80 | # ifdef USE_IOH 81 | void init(long, time_t, unsigned); 82 | # else 83 | void init(long, long, char); 84 | # endif 85 | #endif 86 | void init(); 87 | bool isdir(); 88 | bool readable(); 89 | bool writeable(); 90 | off_t fsize(); 91 | time_t fdate(); 92 | void reset_date(const char *); 93 | }; 94 | 95 | #ifdef USE_STRICMP 96 | # define strcasecmp stricmp 97 | # define strncasecmp strnicmp 98 | #endif 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /colors/aqua.col: -------------------------------------------------------------------------------- 1 | # ------------------------------- 2 | # Aqua Color Scheme for MultiMail 3 | # ------------------------------- 4 | # 5 | # The first one I've done for myself. Light colors, with Blue and Cyan. 6 | # This is what I'm using right now. 7 | 8 | Version: 0.51 9 | 10 | # Background colors 11 | Main_Back: Black, Cyan 12 | Main_Border: Blue 13 | Main_BottSeparator: Magenta 14 | 15 | # Bottom help window 16 | BottHelp_Descrip: Black, Cyan 17 | BottHelp_Keys: Red 18 | 19 | # Pop-up help 20 | Help_Border: Blue, Cyan 21 | Help_Text: Black 22 | 23 | # Welcome window (vanity plate) 24 | Welcome_Border: Blue, White 25 | Welcome_Header: Red 26 | Welcome_Text: Black 27 | 28 | # Address book 29 | Address_Border: Blue, Cyan 30 | Address_Descrip: Red 31 | Address_List: Black 32 | 33 | # Warning window 34 | Warn_Text: Black, Cyan 35 | Warn_Keys: Red 36 | 37 | # Letter window 38 | Letter_Text: Black, White 39 | Letter_Quoted: Bold 40 | Letter_Tagline: Blue 41 | Letter_Tearline: Red 42 | Letter_Hidden: 43 | Letter_Origin: Blue 44 | Letter_Border: Reverse 45 | 46 | # Letter header 47 | LH_Text: Blue, Cyan 48 | LH_Msgnum: Black 49 | LH_From: 50 | LH_To: 51 | LH_Subject: 52 | LH_Date: 53 | LH_FlagsHigh: Cyan, Black, Reverse 54 | LH_Flags: White, Cyan 55 | 56 | # Packet list 57 | Packet_Border: Blue, White 58 | Packet_Header: Red 59 | Packet_List: Black 60 | 61 | # Little area list 62 | LittleArea_Header: Blue, Cyan 63 | LittleArea_List: Black 64 | 65 | # Area list 66 | Area_Reply: Blue, White 67 | Area_List: Black 68 | Area_InfoDescrip: Blue 69 | Area_InfoText: Black 70 | Area_TopText: Red 71 | Area_Border: Blue 72 | Area_Header: Magenta 73 | 74 | # Header editor 75 | HeadEdit_Text: Blue, Cyan 76 | HeadEdit_Input1: Black 77 | HeadEdit_Input2: 78 | 79 | # Save filename input 80 | Save_Border: Blue, Cyan 81 | Save_Header: Red 82 | Save_Input: Black 83 | 84 | # Letter list 85 | LettList_Text: Black, White 86 | LettList_Personal: Red 87 | LettList_Border: Blue 88 | LettList_TopText: Red 89 | LettList_Area: Magenta 90 | LettList_Header: Red 91 | 92 | # Taglines 93 | Tag_Border: Blue, Cyan 94 | Tag_Text: Black 95 | Tag_Keys: Red 96 | Tag_Input1: 97 | Tag_Input2: Black 98 | Tag_List: 99 | 100 | # Shadows 101 | Shadow: Black, Black, Bold 102 | -------------------------------------------------------------------------------- /interfac/main.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * main, error 4 | 5 | Copyright 1996-1997 Kolossvary Tamas 6 | Copyright 1997-2021 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #include "error.h" 10 | #include "interfac.h" 11 | 12 | #include 13 | 14 | const chtype *ColorArray = 0; 15 | time_t starttime; 16 | ErrorType error; 17 | mmail mm; 18 | Interface ui; 19 | 20 | #ifdef USE_MOUSE 21 | MEVENT mm_mouse_event; 22 | #endif 23 | 24 | ErrorType::ErrorType() 25 | { 26 | starttime = time(0); 27 | srand((unsigned) starttime); 28 | 29 | origdir = mygetcwd(); 30 | } 31 | 32 | ErrorType::~ErrorType() 33 | { 34 | mychdir(origdir); 35 | delete[] origdir; 36 | } 37 | 38 | const char *ErrorType::getOrigDir() 39 | { 40 | return origdir; 41 | } 42 | 43 | #if defined(SIGWINCH) && !defined(PDCURSES) && !defined(NCURSES_SIGWINCH) 44 | extern "C" void sigwinchHandler(int sig) 45 | { 46 | if (sig == SIGWINCH) 47 | ungetch(KEY_RESIZE); 48 | signal(SIGWINCH, sigwinchHandler); 49 | } 50 | #endif 51 | 52 | void fatalError(const char *description) 53 | { 54 | if (ui.on && !isendwin()) 55 | ui.close(); 56 | fprintf(stderr, "\n\n%s\n\n", description); 57 | exit(EXIT_FAILURE); 58 | } 59 | 60 | void pauseError(const char *description) 61 | { 62 | fprintf(stderr, "\n\n%s\n\n", description); 63 | napms(2000); 64 | } 65 | 66 | #ifdef USE_MOUSE 67 | void mm_mouse_get() 68 | { 69 | # ifdef NCURSES_MOUSE_VERSION 70 | getmouse(&mm_mouse_event); 71 | # else 72 | nc_getmouse(&mm_mouse_event); 73 | # endif 74 | } 75 | #endif 76 | 77 | int main(int argc, char **argv) 78 | { 79 | setlocale(LC_ALL, ""); 80 | 81 | while ((argc > 2) && ('-' == argv[1][0])) { 82 | char *resName = argv[1] + 1; 83 | char *resValue = argv[2]; 84 | 85 | if ('-' == *resName) 86 | resName++; 87 | 88 | mm.res.processOneByName(resName, resValue); 89 | 90 | argv += 2; 91 | argc -= 2; 92 | } 93 | 94 | ui.init(); 95 | if (argc > 1) 96 | for (int i = 1; (i < argc) && 97 | ui.fromCommandLine(argv[i]); i++); 98 | else 99 | ui.main(); 100 | ui.close(); 101 | 102 | return EXIT_SUCCESS; 103 | } 104 | -------------------------------------------------------------------------------- /mmail/soup.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * SOUP 4 | 5 | Copyright 1999-2021 William McBrine 6 | Distributed under the GNU General Public License, version 3 or later. */ 7 | 8 | #ifndef SOUP_H 9 | #define SOUP_H 10 | 11 | #include "pktbase.h" 12 | 13 | class sheader { 14 | enum {date, from, to, reply, subject, newsgrps, follow, refs, 15 | msgid, items}; 16 | static const char *compare[items]; 17 | char *values[items]; 18 | public: 19 | long msglen; 20 | bool has8bit, qpenc; 21 | 22 | sheader(); 23 | ~sheader(); 24 | bool init(FILE *); 25 | bool init(const char *, const char *, const char *, const char *, 26 | const char *, const char *, long); 27 | void output(FILE *, const char *, bool, bool); 28 | const char *From(); 29 | const char *Subject(); 30 | const char *Date(); 31 | const char *ReplyTo(); 32 | const char *To(); 33 | const char *Newsgrps(); 34 | const char *Follow(); 35 | const char *Msgid(); 36 | const char *Refs(); 37 | }; 38 | 39 | class soup : public pktbase 40 | { 41 | struct AREAs { 42 | char *name; 43 | int nummsgs; 44 | unsigned long attr; 45 | char mode; 46 | char numA[10], msgfile[10]; 47 | AREAs *next; 48 | } **areas; 49 | 50 | bool msgopen(int); 51 | bool parseFrom(const char *); 52 | void buildIndices(); 53 | void readAreas(); 54 | public: 55 | soup(); 56 | ~soup(); 57 | area_header *getNextArea(); 58 | int getNoOfLetters(); 59 | letter_header *getNextLetter(); 60 | letter_body *getBody(letter_header &); 61 | const char *getTear(int); 62 | bool isLatin(); 63 | }; 64 | 65 | class souprep : public pktreply 66 | { 67 | class upl_soup : public upl_base 68 | { 69 | public: 70 | sheader sHead; 71 | net_address na; 72 | int origArea; 73 | long refnum; 74 | bool privat; 75 | 76 | upl_soup(const char * = 0); 77 | }; 78 | bool getRep1(FILE *, upl_soup *); 79 | void getReplies(FILE *); 80 | void addRep1(FILE *, upl_base *, int); 81 | void addHeader(FILE *); 82 | void repFileName(); 83 | const char *repTemplate(bool); 84 | public: 85 | souprep(); 86 | ~souprep(); 87 | area_header *getNextArea(); 88 | letter_header *getNextLetter(); 89 | void enterLetter(letter_header &, const char *, long); 90 | bool getOffConfig(); 91 | bool makeOffConfig(); 92 | }; 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /mmail/netadd.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * net_address class 4 | 5 | Copyright 2021 William McBrine 6 | Distributed under the GNU General Public License, version 3 or later. */ 7 | 8 | #include "mmail.h" 9 | 10 | net_address::net_address() 11 | { 12 | isSet = isInternet = false; 13 | zone = 0; 14 | inetAddr = 0; 15 | } 16 | 17 | net_address::net_address(net_address &x) 18 | { 19 | isSet = isInternet = false; 20 | zone = 0; 21 | inetAddr = 0; 22 | copy(x); 23 | } 24 | 25 | net_address::~net_address() 26 | { 27 | if (isSet && isInternet) 28 | delete[] inetAddr; 29 | } 30 | 31 | bool net_address::operator==(net_address &x) 32 | { 33 | if (isInternet != x.isInternet) 34 | return false; 35 | 36 | if (isInternet) 37 | return !strcmp(inetAddr, x.inetAddr); 38 | else 39 | return (zone == x.zone) && (net == x.net) && 40 | (node == x.node) && (point == x.point); 41 | } 42 | 43 | net_address &net_address::operator=(const char *source) 44 | { 45 | isInternet = source ? !(!strchr(source, '@')) : false; 46 | 47 | if (isInternet) { 48 | delete[] inetAddr; 49 | inetAddr = strdupplus(source); 50 | isSet = true; 51 | } else { 52 | if (sscanf(source ? source : "", "%u:%u/%u.%u", 53 | &zone, &net, &node, &point) == 3) 54 | point = 0; 55 | 56 | isSet = !(!zone); 57 | } 58 | 59 | return *this; 60 | } 61 | 62 | void net_address::copy(net_address &x) 63 | { 64 | isSet = x.isSet; 65 | if (isSet) { 66 | isInternet = x.isInternet; 67 | if (isInternet) 68 | inetAddr = strdupplus(x.inetAddr); 69 | else { 70 | zone = x.zone; 71 | net = x.net; 72 | node = x.node; 73 | point = x.point; 74 | } 75 | } 76 | } 77 | 78 | net_address &net_address::operator=(net_address &x) 79 | { 80 | copy(x); 81 | return *this; 82 | } 83 | 84 | net_address::operator const char *() 85 | { 86 | static char netText[25]; 87 | 88 | if (isSet) 89 | if (isInternet) 90 | return inetAddr; 91 | else 92 | if (point) 93 | sprintf(netText, "%u:%u/%u.%u", zone, net, node, point); 94 | else 95 | sprintf(netText, "%u:%u/%u", zone, net, node); 96 | else 97 | netText[0] = '\0'; 98 | 99 | return netText; 100 | } 101 | -------------------------------------------------------------------------------- /colors/ingo.col: -------------------------------------------------------------------------------- 1 | # ------------------------------------- 2 | # Ingo Brueckl's MultiMail Color Scheme 3 | # ------------------------------------- 4 | # 5 | # More or less. (Some things don't translate.) 6 | 7 | Version: 0.51 8 | 9 | # Background colors 10 | Main_Back: White, Black 11 | Main_Border: Blue, Bold 12 | Main_BottSeparator: Magenta 13 | 14 | # Bottom help window 15 | BottHelp_Descrip: White, Black, Bold 16 | BottHelp_Keys: Yellow, Bold 17 | 18 | # Pop-up help 19 | Help_Border: White, Blue, Bold 20 | Help_Text: Yellow, Bold 21 | 22 | # Welcome window (vanity plate) 23 | Welcome_Border: Blue, Black, Bold 24 | Welcome_Header: Yellow, Bold 25 | Welcome_Text: Cyan, Bold 26 | 27 | # Address book 28 | Address_Border: Red, Black 29 | Address_Descrip: White, Bold 30 | Address_List: Blue, Bold 31 | 32 | # Warning window 33 | Warn_Text: White, Red, Bold 34 | Warn_Keys: Yellow, Bold 35 | 36 | # Letter window 37 | Letter_Text: White, Black 38 | Letter_Quoted: Yellow 39 | Letter_Tagline: White 40 | Letter_Tearline: Blue, Bold 41 | Letter_Hidden: Bold 42 | Letter_Origin: Green, Bold 43 | Letter_Border: Yellow, Magenta, Bold 44 | 45 | # Letter header 46 | LH_Text: White, Cyan, Bold 47 | LH_Msgnum: Green, Bold 48 | LH_From: Blue 49 | LH_To: 50 | LH_Subject: Black 51 | LH_Date: Green, Bold 52 | LH_FlagsHigh: Yellow, Bold 53 | LH_Flags: White 54 | 55 | # Packet list 56 | Packet_Border: Yellow, Blue, Bold 57 | Packet_Header: Green, Bold 58 | Packet_List: Cyan, Bold 59 | 60 | # Little area list 61 | LittleArea_Header: Yellow, Blue, Bold 62 | LittleArea_List: White, Bold 63 | 64 | # Area list 65 | Area_Reply: Magenta, Black 66 | Area_List: Cyan 67 | Area_InfoDescrip: Yellow, Bold 68 | Area_InfoText: White, Bold 69 | Area_TopText: Bold 70 | Area_Border: Blue, Bold 71 | Area_Header: Green, Bold 72 | 73 | # Header editor 74 | HeadEdit_Text: White, Blue, Bold 75 | HeadEdit_Input1: Cyan, Bold 76 | HeadEdit_Input2: Yellow, Bold 77 | 78 | # Save filename input 79 | Save_Border: Yellow, Blue, Bold 80 | Save_Header: White, Bold 81 | Save_Input: Cyan, Bold 82 | 83 | # Letter list 84 | LettList_Text: Cyan, Blue 85 | LettList_Personal: Magenta 86 | LettList_Border: Yellow, Bold 87 | LettList_TopText: White, Bold 88 | LettList_Area: Yellow, Bold 89 | LettList_Header: Green, Bold 90 | LettList_Lines: Cyan 91 | 92 | # Taglines 93 | Tag_Border: Red, Black 94 | Tag_Text: White, Bold 95 | Tag_Keys: Yellow, Bold 96 | Tag_Input1: Cyan, Bold 97 | Tag_Input2: Blue, Bold 98 | Tag_List: Bold 99 | 100 | # Shadows 101 | Shadow: Black, Black, Bold 102 | -------------------------------------------------------------------------------- /mmail/resource.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * resource class 4 | 5 | Copyright 1996-1997 Toth Istvan 6 | Copyright 1997-2017 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #ifndef RESOURCE_H 10 | #define RESOURCE_H 11 | 12 | enum { 13 | UserName, InetAddr, QuoteHead, InetQuote, outCharset, noOfRaw 14 | }; 15 | 16 | enum { 17 | homeDir = noOfRaw, mmHomeDir, PacketDir, TempDir, BaseDir, WorkDir, 18 | UncompressCommand, PacketName, ReplyDir, CompressCommand, UpWorkDir, 19 | editor, SaveDir, AddressFile, TaglineFile, arjUncompressCommand, 20 | zipUncompressCommand, lhaUncompressCommand, rarUncompressCommand, 21 | tarUncompressCommand, unknownUncompressCommand, arjCompressCommand, 22 | zipCompressCommand, lhaCompressCommand, rarCompressCommand, 23 | tarCompressCommand, unknownCompressCommand, sigFile, ColorFile, 24 | oldPacketName, noOfStrings 25 | }; 26 | 27 | enum { 28 | PacketSort = noOfStrings, AreaMode, LetterSort, LetterMode, 29 | Charset, UseTaglines, AutoSaveReplies, StripSoftCR, BeepOnPers, 30 | UseLynxNav, ReOnReplies, QuoteWrapCols, MaxLines, UseQPMailHead, 31 | UseQPNewsHead, UseQPMail, UseQPNews, ExpertMode, IgnoreNDX, Mouse, 32 | #ifdef USE_SPAWNO 33 | swapOut, 34 | #endif 35 | UseColors, Transparency, BackFill, ClockMode, noOfResources 36 | }; 37 | 38 | class baseconfig 39 | { 40 | protected: 41 | const char **names, **comments, **intro; 42 | int configItemNum; 43 | 44 | bool parseConfig(const char *); 45 | void newConfig(const char *); 46 | virtual void processOne(int, const char *) = 0; 47 | virtual const char *configLineOut(int) = 0; 48 | public: 49 | void processOneByName(const char *, const char *); 50 | virtual ~baseconfig(); 51 | }; 52 | 53 | class resource : public baseconfig 54 | { 55 | static const char *rc_names[], *rc_intro[], *rc_comments[]; 56 | static const int startUp[], defInt[]; 57 | 58 | char *resourceData[noOfStrings]; 59 | int resourceInt[noOfResources - noOfStrings]; 60 | 61 | void homeInit(); 62 | void mmEachInit(int, const char *); 63 | void subPath(int, const char *); 64 | void initinit(); 65 | void mmHomeInit(); 66 | void processOne(int, const char *); 67 | const char *configLineOut(int); 68 | bool checkPath(const char *, bool); 69 | bool verifyPaths(); 70 | public: 71 | resource(); 72 | ~resource(); 73 | const char *get(int) const; 74 | int getInt(int) const; 75 | void set(int, const char *); 76 | void set_noalloc(int, char *); 77 | void set(int, int); 78 | }; 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /colors/ver01.col: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------- 2 | # Version 0.1 MultiMail Color Scheme (after Kolossvary Tamas) 3 | # ----------------------------------------------------------- 4 | # 5 | # This is an approximation of the look of the version 0.1x series. (Some 6 | # things aren't translatable.) 7 | 8 | Version: 0.51 9 | 10 | # Background colors 11 | Main_Back: White, Black 12 | Main_Border: Blue, Bold 13 | Main_BottSeparator: Magenta 14 | 15 | # Bottom help window 16 | BottHelp_Descrip: White, Black, Bold 17 | BottHelp_Keys: Yellow, Bold 18 | 19 | # Pop-up help 20 | Help_Border: White, Blue, Bold 21 | Help_Text: Yellow, Bold 22 | 23 | # Welcome window (vanity plate) 24 | Welcome_Border: Yellow, Blue, Bold 25 | Welcome_Header: Bold 26 | Welcome_Text: Cyan, Bold 27 | 28 | # Address book 29 | Address_Border: White, Green, Bold 30 | Address_Descrip: Green, Bold 31 | Address_List: Red 32 | 33 | # Warning window 34 | Warn_Text: White, Red, Bold 35 | Warn_Keys: Yellow, Bold 36 | 37 | # Letter window 38 | Letter_Text: Green, Black 39 | Letter_Quoted: White 40 | Letter_Tagline: Magenta, Bold 41 | Letter_Tearline: Red 42 | Letter_Hidden: 43 | Letter_Origin: White, Bold 44 | Letter_Border: Yellow, Red, Bold 45 | 46 | # Letter header 47 | LH_Text: Blue, Black, Bold 48 | LH_Msgnum: Red 49 | LH_From: Green 50 | LH_To: White 51 | LH_Subject: Yellow, Bold 52 | LH_Date: Yellow, Bold 53 | LH_FlagsHigh: Bold 54 | LH_Flags: 55 | 56 | # Packet list 57 | Packet_Border: Yellow, Blue, Bold 58 | Packet_Header: Green, Bold 59 | Packet_List: Cyan, Bold 60 | 61 | # Little area list 62 | LittleArea_Header: White, Blue, Bold 63 | LittleArea_List: Yellow, Bold 64 | 65 | # Area list 66 | Area_Reply: Yellow, Black 67 | Area_List: White 68 | Area_InfoDescrip: Yellow, Bold 69 | Area_InfoText: White, Bold 70 | Area_TopText: Green, Bold 71 | Area_Border: Yellow, Bold 72 | Area_Header: Magenta 73 | 74 | # Header editor 75 | HeadEdit_Text: White, Blue, Bold 76 | HeadEdit_Input1: Cyan 77 | HeadEdit_Input2: Green, Bold 78 | 79 | # Save filename input 80 | Save_Border: White, Red, Bold 81 | Save_Header: Bold 82 | Save_Input: Yellow, Bold 83 | 84 | # Letter list 85 | LettList_Text: White, Blue 86 | LettList_Personal: Green 87 | LettList_Border: Yellow, Bold 88 | LettList_TopText: White, Bold 89 | LettList_Area: Green, Bold 90 | LettList_Header: Yellow, Bold 91 | LettList_Lines: White 92 | 93 | # Taglines 94 | Tag_Border: Yellow, Yellow, Bold 95 | Tag_Text: White, Bold 96 | Tag_Keys: Green, Bold 97 | Tag_Input1: Bold 98 | Tag_Input2: Magenta, Bold 99 | Tag_List: Bold 100 | 101 | # Shadows 102 | Shadow: Black, Black, Bold 103 | -------------------------------------------------------------------------------- /colors/gilmore.col: -------------------------------------------------------------------------------- 1 | # ------------------------------------- 2 | # Gary Gilmore's MultiMail Color Scheme 3 | # ------------------------------------- 4 | 5 | Version: 0.51 6 | 7 | # Background colors 8 | Main_Back: White, Black 9 | Main_Border: Blue, Black, Bold 10 | Main_BottSeparator: Magenta, Black 11 | 12 | # Bottom help window 13 | BottHelp_Descrip: White, Black, Bold 14 | BottHelp_Keys: Yellow, Black, Bold 15 | 16 | # Pop-up help 17 | Help_Border: White, Blue, Bold 18 | Help_Text: Yellow, Blue, Bold 19 | 20 | # Welcome window (vanity plate) 21 | Welcome_Border: Yellow, Blue, Bold 22 | Welcome_Header: Yellow, Blue, Bold 23 | Welcome_Text: Cyan, Blue, Bold 24 | 25 | # Address book 26 | Address_Border: White, Green, Bold 27 | Address_Descrip: Green, Green, Bold 28 | Address_List: Red, Green 29 | 30 | # Warning window 31 | Warn_Text: White, Red, Bold 32 | Warn_Keys: Yellow, Red, Bold 33 | 34 | # Letter window 35 | Letter_Text: Cyan, Black 36 | Letter_Quoted: White, Black 37 | Letter_Tagline: Cyan, Black 38 | Letter_Tearline: Cyan, Black 39 | Letter_Hidden: Magenta, Black, Bold 40 | Letter_Origin: Cyan, Black 41 | Letter_Border: Yellow, Blue, Bold 42 | 43 | # Letter header 44 | LH_Text: White, Blue, Bold 45 | LH_Msgnum: Red, Blue, Bold 46 | LH_From: Yellow, Blue, Bold 47 | LH_To: Yellow, Blue, Bold 48 | LH_Subject: Yellow, Blue, Bold 49 | LH_Date: White, Blue, Bold 50 | LH_FlagsHigh: Yellow, Blue, Bold 51 | LH_Flags: White, Blue 52 | 53 | # Packet list 54 | Packet_Border: Yellow, Blue, Bold 55 | Packet_Header: Green, Blue, Bold 56 | Packet_List: Cyan, Blue, Bold 57 | 58 | # Little area list 59 | LittleArea_Header: White, Blue, Bold 60 | LittleArea_List: White, Blue, Bold 61 | 62 | # Area list 63 | Area_Reply: Green, Black 64 | Area_List: Cyan, Black 65 | Area_InfoDescrip: Yellow, Blue, Bold 66 | Area_InfoText: White, Blue, Bold 67 | Area_TopText: Yellow, Red, Bold 68 | Area_Border: Yellow, Blue, Bold 69 | Area_Header: Magenta, Blue, Bold 70 | 71 | # Header editor 72 | HeadEdit_Text: White, Blue, Bold 73 | HeadEdit_Input1: Yellow, Black, Bold 74 | HeadEdit_Input2: Yellow, Black, Bold 75 | 76 | # Save filename input 77 | Save_Border: White, Red, Bold 78 | Save_Header: White, Red, Bold 79 | Save_Input: Yellow, Red, Bold 80 | 81 | # Letter list 82 | LettList_Text: White, Blue 83 | LettList_Personal: Green, Blue 84 | LettList_Border: Yellow, Blue, Bold 85 | LettList_TopText: White, Blue, Bold 86 | LettList_Area: Green, Blue, Bold 87 | LettList_Header: Yellow, Blue, Bold 88 | 89 | # Taglines 90 | Tag_Border: Yellow, Blue, Bold 91 | Tag_Text: White, Blue, Bold 92 | Tag_Keys: Red, Blue, Bold 93 | Tag_Input1: Green, Yellow, Bold 94 | Tag_Input2: Yellow, Blue, Bold 95 | Tag_List: Yellow, Black, Bold 96 | 97 | # Shadows 98 | Shadow: Black, Black, Bold 99 | -------------------------------------------------------------------------------- /colors/tradit.col: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------- 2 | # Traditional MultiMail Color Scheme (after John Zero) 3 | # ---------------------------------------------------- 4 | # 5 | # This is basically the scheme used since version 0.2. It's the same as 6 | # the default scheme -- though that may change in the future. 7 | 8 | Version: 0.51 9 | 10 | # Background colors 11 | Main_Back: White, Black 12 | Main_Border: Blue, Bold 13 | Main_BottSeparator: Magenta 14 | 15 | # Bottom help window 16 | BottHelp_Descrip: White, Black, Bold 17 | BottHelp_Keys: Yellow, Bold 18 | 19 | # Pop-up help 20 | Help_Border: White, Blue, Bold 21 | Help_Text: Yellow, Bold 22 | 23 | # Welcome window (vanity plate) 24 | Welcome_Border: Yellow, Blue, Bold 25 | Welcome_Header: Bold 26 | Welcome_Text: Cyan, Bold 27 | 28 | # Address book 29 | Address_Border: White, Green, Bold 30 | Address_Descrip: Green, Bold 31 | Address_List: Red 32 | 33 | # Warning window 34 | Warn_Text: White, Red, Bold 35 | Warn_Keys: Yellow, Bold 36 | 37 | # Letter window 38 | Letter_Text: White, Blue, Bold 39 | Letter_Quoted: 40 | Letter_Tagline: Cyan, Bold 41 | Letter_Tearline: Blue, Bold 42 | Letter_Hidden: Bold 43 | Letter_Origin: Bold 44 | Letter_Border: Magenta, White, Reverse 45 | 46 | # Letter header 47 | LH_Text: Blue, Cyan 48 | LH_Msgnum: White, Bold 49 | LH_From: Green, Bold 50 | LH_To: White, Bold 51 | LH_Subject: Blue, Bold 52 | LH_Date: White, Bold 53 | LH_FlagsHigh: Yellow, Bold 54 | LH_Flags: White 55 | 56 | # Packet list 57 | Packet_Border: Yellow, Blue, Bold 58 | Packet_Header: Green, Bold 59 | Packet_List: Cyan, Bold 60 | 61 | # Little area list 62 | LittleArea_Header: White, Blue, Bold 63 | LittleArea_List: Bold 64 | 65 | # Area list 66 | Area_Reply: Yellow, Blue 67 | Area_List: Cyan 68 | Area_InfoDescrip: Yellow, Bold 69 | Area_InfoText: White, Bold 70 | Area_TopText: Green, Bold 71 | Area_Border: Yellow, Bold 72 | Area_Header: Magenta, Bold 73 | 74 | # Header editor 75 | HeadEdit_Text: White, Blue, Bold 76 | HeadEdit_Input1: Cyan 77 | HeadEdit_Input2: Green, Bold 78 | 79 | # Save filename input 80 | Save_Border: White, Red, Bold 81 | Save_Header: Bold 82 | Save_Input: Yellow, Bold 83 | 84 | # Letter list 85 | LettList_Text: White, Blue 86 | LettList_Personal: Green 87 | LettList_Border: Yellow, Bold 88 | LettList_TopText: White, Bold 89 | LettList_Area: Green, Bold 90 | LettList_Header: Yellow, Bold 91 | LettList_Lines: White 92 | 93 | # Taglines 94 | Tag_Border: Yellow, Yellow, Bold 95 | Tag_Text: White, Bold 96 | Tag_Keys: Green, Bold 97 | Tag_Input1: Bold 98 | Tag_Input2: Magenta, Bold 99 | Tag_List: Bold 100 | 101 | # Shadows 102 | Shadow: Black, Black, Bold 103 | -------------------------------------------------------------------------------- /colors/tonys.col: -------------------------------------------------------------------------------- 1 | # ---------------------------------------- 2 | # Tony Summerfelt's MultiMail Color Scheme 3 | # ---------------------------------------- 4 | 5 | Version: 0.51 6 | 7 | # Background colors 8 | Main_Back: White, Black 9 | Main_Border: Cyan, Black, Bold 10 | Main_BottSeparator: Cyan, Black, Bold 11 | 12 | # Bottom help window 13 | BottHelp_Descrip: White, Black, Bold 14 | BottHelp_Keys: Yellow, Black, Bold 15 | 16 | # Pop-up help 17 | Help_Border: Cyan, Black, Bold 18 | Help_Text: Red, Black, Bold 19 | 20 | # Welcome window (vanity plate) 21 | Welcome_Border: Cyan, Black, Bold 22 | Welcome_Header: Yellow, Black, Bold 23 | Welcome_Text: Red, Black, Bold 24 | 25 | # Address book 26 | Address_Border: Cyan, Black, Bold 27 | Address_Descrip: Red, Black, Bold 28 | Address_List: Red, Black, Bold 29 | 30 | # Warning window 31 | Warn_Text: Yellow, Red, Bold 32 | Warn_Keys: Yellow, Red, Bold 33 | 34 | # Letter window 35 | Letter_Text: Red, Black, Bold 36 | Letter_Quoted: Yellow, Black, Bold 37 | Letter_Tagline: Cyan, Black, Bold 38 | Letter_Tearline: Magenta, Black, Bold 39 | Letter_Hidden: Magenta, Black, Bold 40 | Letter_Origin: Cyan, Black 41 | Letter_Border: Cyan, Black, Bold 42 | 43 | # Letter header 44 | LH_Text: Cyan, Blue, Bold 45 | LH_Msgnum: White, Blue, Bold 46 | LH_From: Cyan, Blue, Bold 47 | LH_To: Cyan, Blue, Bold 48 | LH_Subject: Cyan, Blue, Bold 49 | LH_Date: White, Blue, Bold 50 | LH_FlagsHigh: Yellow, Blue, Bold 51 | LH_Flags: White, Blue, Bold 52 | 53 | # Packet list 54 | Packet_Border: Cyan, Black, Bold 55 | Packet_Header: Yellow, Black, Bold 56 | Packet_List: Red, Black, Bold 57 | 58 | # Little area list 59 | LittleArea_Header: Yellow, Black, Bold 60 | LittleArea_List: Red, Black, Bold 61 | 62 | # Area list 63 | Area_Reply: Red, Black 64 | Area_List: Red, Black 65 | Area_InfoDescrip: Red, Black, Bold 66 | Area_InfoText: Red, Black, Bold 67 | Area_TopText: Yellow, Black, Bold 68 | Area_Border: Cyan, Black, Bold 69 | Area_Header: Yellow, Black, Bold 70 | 71 | # Header editor 72 | HeadEdit_Text: Yellow, Black, Bold 73 | HeadEdit_Input1: Red, Black, Bold 74 | HeadEdit_Input2: Red, Black, Bold 75 | 76 | # Save filename input 77 | Save_Border: Cyan, Black, Bold 78 | Save_Header: Red, Black, Bold 79 | Save_Input: Red, Black, Bold 80 | 81 | # Letter list 82 | LettList_Text: Red, Black 83 | LettList_Personal: Red, Black 84 | LettList_Border: Cyan, Black, Bold 85 | LettList_TopText: Yellow, Black, Bold 86 | LettList_Area: White, Black, Bold 87 | LettList_Header: Yellow, Black, Bold 88 | 89 | # Taglines 90 | Tag_Border: Cyan, Black, Bold 91 | Tag_Text: Red, Black, Bold 92 | Tag_Keys: Yellow, Black, Bold 93 | Tag_Input1: Cyan, Blue, Bold 94 | Tag_Input2: Cyan, Blue, Bold 95 | Tag_List: Red, Black, Bold 96 | 97 | # Shadows 98 | Shadow: Black, Black, Bold 99 | -------------------------------------------------------------------------------- /colors/holger2.col: -------------------------------------------------------------------------------- 1 | # ---------------------------------------- 2 | # Holger Granholm's MultiMail Color Scheme 3 | # ---------------------------------------- 4 | 5 | Version: 0.51 6 | 7 | # Background colors 8 | Main_Back: White, Black 9 | Main_Border: Blue, Black, Bold 10 | Main_BottSeparator: Magenta, Black 11 | 12 | # Bottom help window 13 | BottHelp_Descrip: White, Black, Bold 14 | BottHelp_Keys: Yellow, Black, Bold 15 | 16 | # Pop-up help 17 | Help_Border: White, Blue, Bold 18 | Help_Text: Yellow, Blue, Bold 19 | 20 | # Welcome window (vanity plate) 21 | Welcome_Border: Yellow, Blue, Bold 22 | Welcome_Header: Yellow, Blue, Bold 23 | Welcome_Text: Cyan, Blue, Bold 24 | 25 | # Address book 26 | Address_Border: White, Green, Bold 27 | Address_Descrip: Green, Green, Bold 28 | Address_List: Red, Green 29 | 30 | # Warning window 31 | Warn_Text: White, Red, Bold 32 | Warn_Keys: Yellow, Red, Bold 33 | 34 | # Letter window 35 | Letter_Text: Cyan, Blue, Bold 36 | Letter_Quoted: Green, Blue, Bold 37 | Letter_Tagline: Magenta, Blue, Bold 38 | Letter_Tearline: Red, Blue, Bold 39 | Letter_Hidden: Red, Blue 40 | Letter_Origin: White, Blue, Bold 41 | Letter_Border: Yellow, Red, Bold 42 | 43 | # Letter header 44 | LH_Text: White, Black 45 | LH_Msgnum: Red, Black, Bold 46 | LH_From: Cyan, Black, Bold 47 | LH_To: Green, Black, Bold 48 | LH_Subject: Yellow, Black, Bold 49 | LH_Date: Yellow, Black, Bold 50 | LH_FlagsHigh: Yellow, Black, Bold 51 | LH_Flags: Yellow, Black 52 | 53 | # Packet list 54 | Packet_Border: Yellow, Blue, Bold 55 | Packet_Header: Green, Blue, Bold 56 | Packet_List: Cyan, Blue, Bold 57 | 58 | # Little area list 59 | LittleArea_Header: White, Blue, Bold 60 | LittleArea_List: Yellow, Blue, Bold 61 | 62 | # Area list 63 | Area_Reply: Yellow, Black, Bold 64 | Area_List: White, Black 65 | Area_InfoDescrip: Yellow, Black, Bold 66 | Area_InfoText: White, Black, Bold 67 | Area_TopText: Cyan, Black, Bold 68 | Area_Border: Yellow, Black, Bold 69 | Area_Header: White, Black 70 | 71 | # Header editor 72 | HeadEdit_Text: White, Blue, Bold 73 | HeadEdit_Input1: Cyan, Blue 74 | HeadEdit_Input2: Green, Blue, Bold 75 | 76 | # Save filename input 77 | Save_Border: White, Red, Bold 78 | Save_Header: White, Red, Bold 79 | Save_Input: Yellow, Red, Bold 80 | 81 | # Letter list 82 | LettList_Text: White, Blue 83 | LettList_Personal: Cyan, Blue, Bold 84 | LettList_Border: Yellow, Blue, Bold 85 | LettList_TopText: White, Blue, Bold 86 | LettList_Area: Green, Blue, Bold 87 | LettList_Header: Yellow, Blue, Bold 88 | 89 | # Taglines 90 | Tag_Border: Yellow, Yellow, Bold 91 | Tag_Text: White, Yellow, Bold 92 | Tag_Keys: Green, Yellow, Bold 93 | Tag_Input1: Green, Yellow, Bold 94 | Tag_Input2: Magenta, Yellow, Bold 95 | Tag_List: White, Blue, Bold 96 | 97 | # Shadows 98 | Shadow: Black, Black, Bold 99 | -------------------------------------------------------------------------------- /colors/toutant.col: -------------------------------------------------------------------------------- 1 | # -------------------------------------- 2 | # David Toutant's MultiMail Color Scheme 3 | # -------------------------------------- 4 | 5 | Version: 0.51 6 | 7 | # Background colors 8 | Main_Back: White, Black, Bold 9 | Main_Border: Blue, Black, Bold 10 | Main_BottSeparator: Magenta, Black 11 | 12 | # Bottom help window 13 | BottHelp_Descrip: White, Black, Bold 14 | BottHelp_Keys: Yellow, Black, Bold 15 | 16 | # Pop-up help 17 | Help_Border: White, Blue, Bold 18 | Help_Text: Yellow, Blue, Bold 19 | 20 | # Welcome window (vanity plate) 21 | Welcome_Border: Blue, Black, Bold 22 | Welcome_Header: Yellow, Black, Bold 23 | Welcome_Text: Green, Black, Bold 24 | 25 | # Address book 26 | Address_Border: Red, Black 27 | Address_Descrip: White, Black, Bold 28 | Address_List: Blue, Black, Bold 29 | 30 | # Warning window 31 | Warn_Text: White, Red, Bold 32 | Warn_Keys: Yellow, Red, Bold 33 | 34 | # Letter window 35 | Letter_Text: Black, Cyan 36 | Letter_Quoted: White, Cyan, Bold 37 | Letter_Tagline: Red, Cyan 38 | Letter_Tearline: Yellow, Cyan, Bold 39 | Letter_Hidden: Blue, Cyan, Bold 40 | Letter_Origin: Green, Cyan, Bold 41 | Letter_Border: Yellow, Magenta, Bold 42 | 43 | # Letter header 44 | LH_Text: White, Black, Bold 45 | LH_Msgnum: Green, Black, Bold 46 | LH_From: Magenta, Black, Bold 47 | LH_To: White, Black, Bold 48 | LH_Subject: Yellow, Black, Bold 49 | LH_Date: Green, Black, Bold 50 | LH_FlagsHigh: Yellow, Black, Bold 51 | LH_Flags: White, Black, Bold 52 | 53 | # Packet list 54 | Packet_Border: Yellow, Blue, Bold 55 | Packet_Header: Green, Blue, Bold 56 | Packet_List: Cyan, Blue, Bold 57 | 58 | # Little area list 59 | LittleArea_Header: Yellow, Blue, Bold 60 | LittleArea_List: White, Blue, Bold 61 | 62 | # Area list 63 | Area_Reply: Yellow, Blue, Bold 64 | Area_List: White, Blue, Bold 65 | Area_InfoDescrip: Yellow, Blue, Bold 66 | Area_InfoText: White, Blue, Bold 67 | Area_TopText: White, Blue, Bold 68 | Area_Border: Blue, Blue, Bold 69 | Area_Header: Green, Blue, Bold 70 | 71 | # Header editor 72 | HeadEdit_Text: White, Blue, Bold 73 | HeadEdit_Input1: Cyan, Blue, Bold 74 | HeadEdit_Input2: Yellow, Blue, Bold 75 | 76 | # Save filename input 77 | Save_Border: Yellow, Blue, Bold 78 | Save_Header: White, Blue, Bold 79 | Save_Input: Cyan, Blue, Bold 80 | 81 | # Letter list 82 | LettList_Text: Cyan, Blue 83 | LettList_Personal: Yellow, Blue, Bold 84 | LettList_Border: Yellow, Blue, Bold 85 | LettList_TopText: White, Blue, Bold 86 | LettList_Area: Yellow, Blue, Bold 87 | LettList_Header: Green, Blue, Bold 88 | 89 | # Taglines 90 | Tag_Border: Red, Black 91 | Tag_Text: White, Black, Bold 92 | Tag_Keys: Yellow, Black, Bold 93 | Tag_Input1: Cyan, Black, Bold 94 | Tag_Input2: Blue, Black, Bold 95 | Tag_List: Blue, Black, Bold 96 | 97 | # Shadows 98 | Shadow: Black, Black, Bold 99 | -------------------------------------------------------------------------------- /interfac/isoconv.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * conversion tables ISO 8859-1 <-> IBM codepage 437 4 | 5 | Copyright 1997 Peter Krefting , 6 | Toth Istvan 7 | Copyright 1998-2017 William McBrine 8 | Distributed under the GNU General Public License, version 3 or later. */ 9 | 10 | /* Original tables by Peter Krefting, modified by William McBrine after 11 | DOSEmu's video/terminal.h, by Mark D. Rejhon. */ 12 | 13 | #include "interfac.h" 14 | 15 | enum cdirtype {CC_ISOTO437, CC_437TOISO}; 16 | 17 | bool isoConsole; 18 | 19 | const char *dos2isotab = 20 | "\307\374\351\342\344\340\345\347\352\353\350\357\356\354\304\305" 21 | "\311\346\306\364\366\362\373\371\377\326\334\242\243\245\120\146" 22 | "\341\355\363\372\361\321\252\272\277\055\254\275\274\241\253\273" 23 | ":%&|{{{..{I.'''.``+}-+}}`.**}=**+*+``..**'.#_][~" 24 | "a\337\254\266{\363\265t\330\364\326\363o\370En" 25 | "=\261><()\367=\260\267\267%\140\262= "; 26 | 27 | const char *iso2dostab = 28 | "\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217" 29 | "\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237 " 30 | "\255\233\234$\235|\025\"c\246\256\252-r-\370\361\3753\'\346\024\371,1" 31 | "\370\257\254\253/\250AAAA\216\217\222\200E\220EEIIIID\245OOOO\231x" 32 | "\231UUU\232Y \341\205\240\203a\204\206\221\207\212\202\210\211\215\241" 33 | "\214\213 \244\225\242\223o\224\366\224\227\243\226\201y \230"; 34 | 35 | char *charconv(char *buf, cdirtype cdir) 36 | { 37 | const char *ct = (cdir == CC_ISOTO437) ? iso2dostab : dos2isotab; 38 | 39 | for (char *p = buf; *p; p++) { 40 | unsigned char c = *p; 41 | if (c & 0x80) 42 | *p = ct[c & 0x7f]; 43 | } 44 | return buf; 45 | } 46 | 47 | char *charconv_in(char *buf) 48 | { 49 | return (isoConsole ? charconv(buf, CC_437TOISO) : buf); 50 | } 51 | 52 | char *charconv_out(char *buf) 53 | { 54 | return (isoConsole ? charconv(buf, CC_ISOTO437) : buf); 55 | } 56 | 57 | char *letterconv_in(char *buf) 58 | { 59 | return (mm.letterList->isLatin() ^ isoConsole) ? 60 | charconv(buf, isoConsole ? CC_437TOISO : CC_ISOTO437) : buf; 61 | } 62 | 63 | char *letterconv_out(char *buf) 64 | { 65 | return (mm.letterList->isLatin() ^ isoConsole) ? 66 | charconv(buf, isoConsole ? CC_ISOTO437 : CC_437TOISO) : buf; 67 | } 68 | 69 | char *areaconv_in(char *buf) 70 | { 71 | return (mm.areaList->isLatin() ^ isoConsole) ? 72 | charconv(buf, isoConsole ? CC_437TOISO : CC_ISOTO437) : buf; 73 | } 74 | 75 | char *areaconv_out(char *buf) 76 | { 77 | return (mm.areaList->isLatin() ^ isoConsole) ? 78 | charconv(buf, isoConsole ? CC_ISOTO437 : CC_437TOISO) : buf; 79 | } 80 | -------------------------------------------------------------------------------- /colors/tuukka.col: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------- 2 | # Tuukka Lehtinen's MultiMail Color Scheme (Monochrome) 3 | # ----------------------------------------------------- 4 | 5 | Version: 0.51 6 | 7 | # Background colors 8 | Main_Back: White, Black 9 | Main_Border: Black, Black, Bold 10 | Main_BottSeparator: White, Black 11 | 12 | # Bottom help window 13 | BottHelp_Descrip: White, Black, Bold 14 | BottHelp_Keys: White, Black, Bold 15 | 16 | # Pop-up help 17 | Help_Border: White, Black, Bold 18 | Help_Text: White, Black, Bold 19 | 20 | # Welcome window (vanity plate) 21 | Welcome_Border: White, Black, Bold 22 | Welcome_Header: White, Black, Bold 23 | Welcome_Text: White, Black, Bold 24 | 25 | # Address book 26 | Address_Border: White, Black, Bold 27 | Address_Descrip: White, Black, Bold 28 | Address_List: White, Black 29 | 30 | # Warning window 31 | Warn_Text: Black, Black, Bold 32 | Warn_Keys: White, Black, Bold 33 | 34 | # Letter window 35 | Letter_Text: White, Black, Bold 36 | Letter_Quoted: White, Black 37 | Letter_Tagline: White, Black, Bold 38 | Letter_Tearline: White, Black, Bold 39 | Letter_Hidden: White, Black, Bold 40 | Letter_Origin: White, Black, Bold 41 | Letter_Border: White, Black 42 | 43 | # Letter header 44 | LH_Text: White, Black 45 | LH_Msgnum: White, Black, Bold 46 | LH_From: White, Black, Bold 47 | LH_To: White, Black, Bold 48 | LH_Subject: Black, Black, Bold 49 | LH_Date: White, Black, Bold 50 | LH_FlagsHigh: Black, Black, Bold 51 | LH_Flags: White, Black 52 | 53 | # Packet list 54 | Packet_Border: Black, Black, Bold 55 | Packet_Header: White, Black, Bold 56 | Packet_List: White, Black, Bold 57 | 58 | # Little area list 59 | LittleArea_Header: White, Black, Bold 60 | LittleArea_List: White, Black, Bold 61 | 62 | # Area list 63 | Area_Reply: White, Black 64 | Area_List: White, Black 65 | Area_InfoDescrip: Black, Black, Bold 66 | Area_InfoText: White, Black, Bold 67 | Area_TopText: White, Black, Bold 68 | Area_Border: Black, Black, Bold 69 | Area_Header: White, Black, Bold 70 | 71 | # Header editor 72 | HeadEdit_Text: White, Black, Bold 73 | HeadEdit_Input1: White, Black 74 | HeadEdit_Input2: White, Black, Bold 75 | 76 | # Save filename input 77 | Save_Border: White, Black, Bold 78 | Save_Header: White, Black, Bold 79 | Save_Input: White, Black, Bold 80 | 81 | # Letter list 82 | LettList_Text: White, Black 83 | LettList_Personal: White, Black 84 | LettList_Border: Black, Black, Bold 85 | LettList_TopText: White, Black, Bold 86 | LettList_Area: White, Black, Bold 87 | LettList_Header: Black, Black, Bold 88 | 89 | # Taglines 90 | Tag_Border: Black, Black, Bold 91 | Tag_Text: White, Black, Bold 92 | Tag_Keys: White, Black, Bold 93 | Tag_Input1: White, Black, Bold 94 | Tag_Input2: White, Black, Bold 95 | Tag_List: White, Black, Bold 96 | 97 | # Shadows 98 | Shadow: Black, Black, Bold 99 | -------------------------------------------------------------------------------- /mmail/bw.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * Blue Wave 4 | 5 | Copyright 1996-1997 Toth Istvan 6 | Copyright 1998-2021 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #ifndef BW_H 10 | #define BW_H 11 | 12 | #include "pktbase.h" 13 | 14 | #ifndef BIG_ENDIAN 15 | #define BIG_ENDIAN 16 | #endif 17 | 18 | #include "bluewave.h" 19 | 20 | class bluewave : public pktbase 21 | { 22 | FILE *ftiFile; 23 | INF_HEADER infoHeader; 24 | INF_AREA_INFO *areas; 25 | MIX_REC *mixRecord; 26 | 27 | struct perstype { 28 | int area, msgnum; 29 | } *persNdx; 30 | 31 | long personal; 32 | 33 | int *mixID; 34 | int noOfMixRecs; 35 | int from_to_len; 36 | int subject_len; 37 | unsigned infoHeaderLen, infoAreainfoLen, mixStructLen, ftiStructLen; 38 | 39 | FILE *openFile(const char *); 40 | void findInfBaseName(); 41 | void initInf(); 42 | void initMixID(); 43 | 44 | void getblk(int, long &, long, unsigned char *&, unsigned char *&); 45 | void endproc(letter_header &); 46 | public: 47 | bluewave(); 48 | ~bluewave(); 49 | bool hasPersonal(); 50 | area_header *getNextArea(); 51 | int getNoOfLetters(); 52 | letter_header *getNextLetter(); 53 | const char *getTear(int); 54 | const char *oldFlagsName(); 55 | bool readOldFlags(); 56 | bool saveOldFlags(); 57 | INF_HEADER &getInfHeader(); 58 | }; 59 | 60 | class bwreply : public pktreply 61 | { 62 | class upl_bw : public upl_base { 63 | public: 64 | UPL_REC uplRec; 65 | char *msgid, *newsgrps, *extsubj; 66 | 67 | upl_bw(const char * = 0); 68 | ~upl_bw(); 69 | }; 70 | 71 | UPL_HEADER *uplHeader; 72 | 73 | int getAreaFromTag(const char *); 74 | bool getRep1(FILE *, upl_bw *, int); 75 | void getReplies(FILE *); 76 | const char *freeFileName(); 77 | void addRep1(FILE *, upl_base *, int); 78 | void addHeader(FILE *); 79 | void repFileName(); 80 | const char *repTemplate(bool); 81 | char *nextLine(FILE *); 82 | void getOffArea(const char *, int &, int); 83 | void getOLC(FILE *, int &, int); 84 | void getPDQ(FILE *, int &, int); 85 | public: 86 | bwreply(); 87 | ~bwreply(); 88 | area_header *getNextArea(); 89 | letter_header *getNextLetter(); 90 | void enterLetter(letter_header &, const char *, long); 91 | bool getOffConfig(); 92 | bool makeOffConfig(); 93 | }; 94 | 95 | /* To ensure correct operation where alignment padding is used, when 96 | reading from or writing to disk, use the _SIZE defines given here 97 | rather than "sizeof": 98 | */ 99 | 100 | #define UPI_HEAD_SIZE 55 101 | #define PDQ_REC_SIZE 21 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /interfac/mmcolor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * color pairs #define'd here 4 | 5 | Copyright 1996-1997 John Zero 6 | Copyright 1997-2017 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #ifndef MMCOLOR_H 10 | #define MMCOLOR_H 11 | 12 | #define COL(f, b) COLOR_PAIR(((f) << 3) + (b)) 13 | #define REVERSE(f, b) ((COL((f), (b))) | (A_REVERSE)) 14 | 15 | #define C_ANSIBACK COL(COLOR_WHITE, COLOR_BLACK) 16 | 17 | enum coltype { 18 | C_SBACK, //Start screen/backgnd 19 | C_SBORDER, //Start/bdr 20 | C_SSEPBOTT, //Start screen/bottom 21 | C_HELP1, //Help desc. 22 | C_HELP2, //Help keys 23 | C_HELP3, //Help 2 bdr 24 | C_HELP4, //Help 2 text 25 | C_WBORDER, //Welcome border 26 | C_WELCOME1, //Welcome prog name 27 | C_WELCOME2, //Welcome auth names 28 | C_ADDR1, //Add. backgnd 29 | C_ADDR2, //Add. headers 30 | C_ADDR3, //Address book/text 31 | C_WTEXT, //Warn/text 32 | C_WTEXTHI, //Warn/hilight 33 | C_LTEXT, //Letter/text 34 | C_LQTEXT, //Letter/quoted text 35 | C_LTAGLINE, //Letter/tagline 36 | C_LTEAR, //Letter/tear 37 | C_LHIDDEN, //Letter/hidden 38 | C_LORIGIN, //Letter/origin 39 | C_LBOTTSTAT, //Letter/bottom statline 40 | C_LHEADTEXT, //Letter/header text 41 | C_LHMSGNUM, //msgnum 42 | C_LHFROM, //from 43 | C_LHTO, //to 44 | C_LHSUBJ, //subject 45 | C_LHDATE, //date 46 | C_LHFLAGSHI, //flags high 47 | C_LHFLAGS, //flags 48 | C_PBBACK, //Packet/header 49 | C_PHEADTEXT, //line text 50 | C_PLINES, //Packet/lines 51 | C_LALBTEXT, //Little area 52 | C_LALLINES, //line text 53 | C_ALREPLINE, //Area list/reply area 54 | C_ALPACKETLINE, //Area list/normal 55 | C_ALINFOTEXT, //info win 56 | C_ALINFOTEXT2, //filled text 57 | C_ALBTEXT, //border text 58 | C_ALBORDER, //border 59 | C_ALHEADTEXT, //header text 60 | C_LETEXT, //Letter text 61 | C_LEGET1, //Letter/enter get1 62 | C_LEGET2, //get2 63 | C_LLSAVEBORD, //Letter/save border 64 | C_LLSAVETEXT, //Letter/save 65 | C_LLSAVEGET, //get 66 | C_LISTWIN, //Letter list/top text1 67 | C_LLPERSONAL, //Letter list/personal 68 | C_LLBBORD, //Letter list 69 | C_LLTOPTEXT1, //top text1 70 | C_LLTOPTEXT2, //areaname 71 | C_LLHEAD, //headers 72 | C_TBBACK, //Tagline 73 | C_TTEXT, //Tagline/text 74 | C_TKEYSTEXT, //key select 75 | C_TENTER, //Tagline/enter 76 | C_TENTERGET, //enter get 77 | C_TLINES, //lines 78 | C_SHADOW, //All black! 79 | numColors 80 | }; 81 | 82 | chtype emph(coltype); 83 | chtype noemph(coltype); 84 | 85 | extern const chtype *ColorArray; 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /mmail/qwk.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * QWK 4 | 5 | Copyright 1997 John Zero 6 | Copyright 1997-2021 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #ifndef QWK_H 10 | #define QWK_H 11 | 12 | #include "pktbase.h" 13 | 14 | #define ndxRecLen 5 15 | 16 | // Offset of the "chunks" field, so it can be written separately 17 | #define CHUNK_OFFSET 116 18 | 19 | class qheader { 20 | struct qwkmsg_header { 21 | char status; 22 | char msgnum[7]; // in ASCII 23 | char date[8]; // ASCII MM-DD-YY date 24 | char time[5]; // time in HH:MM ASCII 25 | char to[25]; // TO 26 | char from[25]; // FROM 27 | char subject[25]; // subject of message 28 | char password[12]; // message passw. 29 | char refnum[8]; // in ASCII 30 | char chunks[6]; // number of 128 byte chunks 31 | char alive; // msg is alive/killed 32 | unsigned char confLSB; 33 | unsigned char confMSB; 34 | char res[3]; 35 | }; 36 | 37 | public: 38 | char from[72], to[72], subject[72], date[15]; 39 | long msglen; 40 | int origArea; 41 | long msgnum, refnum; 42 | bool privat, netblock; 43 | 44 | bool init(FILE *); 45 | bool init_short(FILE *); 46 | void output(FILE *); 47 | void set_length(FILE *, long, long); 48 | void get_field(char *, const char *, size_t); 49 | }; 50 | 51 | class qwkpack : public pktbase 52 | { 53 | char newsfile[1][13]; 54 | char controlname[26]; 55 | bool qwke, greekqwk; 56 | 57 | unsigned long MSBINtolong(unsigned const char *); 58 | void readControlDat(); 59 | void readDoorId(); 60 | void readToReader(); 61 | bool externalIndex(); 62 | void readIndices(); 63 | 64 | void getblk(int, long &, long, unsigned char *&, unsigned char *&); 65 | void postfirstblk(unsigned char *&, letter_header &); 66 | void endproc(letter_header &); 67 | public: 68 | qwkpack(); 69 | ~qwkpack(); 70 | area_header *getNextArea(); 71 | letter_header *getNextLetter(); 72 | bool isQWKE(); 73 | bool isGreekQWK(); 74 | const char *ctrlName(); 75 | }; 76 | 77 | class qwkreply : public pktreply 78 | { 79 | class upl_qwk : public upl_base { 80 | public: 81 | qheader qHead; 82 | 83 | upl_qwk(const char * = 0); 84 | }; 85 | 86 | bool qwke, greekqwk; 87 | 88 | bool getRep1(FILE *, upl_qwk *); 89 | void getReplies(FILE *); 90 | void addRep1(FILE *, upl_base *, int); 91 | void addHeader(FILE *); 92 | void repFileName(); 93 | const char *repTemplate(bool); 94 | public: 95 | qwkreply(); 96 | ~qwkreply(); 97 | area_header *getNextArea(); 98 | letter_header *getNextLetter(); 99 | void enterLetter(letter_header &, const char *, long); 100 | bool getOffConfig(); 101 | bool makeOffConfig(); 102 | }; 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MultiMail 2 | ========= 3 | 4 | MultiMail is an offline mail packet reader for Unix / Linux, MS-DOS, 5 | OS/2, Windows, macOS, and other systems, using a curses-based interface. 6 | It supports the Blue Wave, QWK, OMEN, SOUP and OPX formats. 7 | 8 | MultiMail is free, open source software, distributed under the [GNU 9 | General Public License][gpl], version 3 or later. 10 | 11 | You can get the latest version at: 12 | 13 | 14 | 15 | See [INSTALL] for the installation procedure, and the [man page] for 16 | information on usage. 17 | 18 | 19 | Version 0.52 - 2019-04-01 20 | ------------------------- 21 | 22 | * Long To and From lines in QWKE replies 23 | * Blue Wave tear lines improved 24 | * Build and documentation cleanups 25 | 26 | See the [HISTORY] file for other changes. 27 | 28 | 29 | Downloads 30 | --------- 31 | 32 | * Source code 33 | - [TAR] 34 | - [ZIP] 35 | * MS-DOS 36 | - [x86-32][dos] Also works under Windows 9x, etc. 37 | - [x86-16][xt] 38 | * OS/2 39 | - [x86-32 VIO][os2] Needs at least OS/2 2.0. 40 | * Windows 41 | - [x86-32 console][w32] Needs 9x or later (tested through 10). 42 | - [x86-64 console][w64] Only tested on Windows 10. 43 | * macOS 44 | - [x86-64 Terminal][mac] Only tested on macOS 10.14. 45 | 46 | 47 | Mailing lists 48 | ------------- 49 | 50 | Join the MultiMail [announce] list and/or the [discussion] list. 51 | 52 | 53 | Other links 54 | ----------- 55 | 56 | * [Screen Shots] 57 | * [GitHub Page] 58 | * [SourceForge Page] 59 | * [Offline Formats] 60 | 61 | 62 | Credits 63 | ------- 64 | 65 | MultiMail was originally developed under Linux by Kolossvary Tamas and 66 | Toth Istvan. John Zero was the maintainer for versions 0.2 through 0.6; 67 | since version 0.7, the maintainer is [William McBrine]. 68 | 69 | Additional code has been contributed by Peter Krefting, Mark D. Rejhon, 70 | Ingo Brueckl, Robert Vukovic, and Frederic Cambus. 71 | 72 | Bug reports and suggestions are noted in the [HISTORY] file. 73 | 74 | 75 | [gpl]: LEGAL.md 76 | [HISTORY]: HISTORY.md 77 | [INSTALL]: INSTALL.md 78 | [man page]: MANUAL.md 79 | 80 | [TAR]: https://github.com/wmcbrine/MultiMail/archive/refs/tags/0.52.tar.gz 81 | [ZIP]: https://github.com/wmcbrine/MultiMail/archive/refs/tags/0.52.zip 82 | 83 | [dos]: https://github.com/wmcbrine/MultiMail/releases/download/0.52/mmdos052.zip 84 | [xt]: https://github.com/wmcbrine/MultiMail/releases/download/0.52/mmxt052.zip 85 | [os2]: https://github.com/wmcbrine/MultiMail/releases/download/0.52/mmos2052.zip 86 | [w32]: https://github.com/wmcbrine/MultiMail/releases/download/0.52/mmwin052.zip 87 | [w64]: https://github.com/wmcbrine/MultiMail/releases/download/0.52/mmw64052.zip 88 | [mac]: https://github.com/wmcbrine/MultiMail/releases/download/0.52/mmmac052.zip 89 | 90 | [Screen Shots]: https://wmcbrine.com/mmail/snaps/ 91 | [GitHub Page]: https://github.com/wmcbrine/MultiMail 92 | [SourceForge Page]: https://sourceforge.net/projects/multimail/ 93 | [Offline Formats]: https://wmcbrine.com/mmail/specs/ 94 | 95 | [announce]: https://lists.sourceforge.net/lists/listinfo/multimail-announce 96 | [discussion]: https://lists.sourceforge.net/lists/listinfo/multimail-user 97 | 98 | [William McBrine]: https://wmcbrine.com/ 99 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #-------------------------- 2 | # MultiMail Makefile (top) 3 | #-------------------------- 4 | 5 | msrc = mmail 6 | isrc = interfac 7 | 8 | # General options (passed to mmail/Makefile and interfac/Makefile). POST 9 | # is for any post-processing that needs doing. 10 | 11 | ifeq ($(DEBUG),Y) 12 | OPTS = -g -Wall -pedantic 13 | else 14 | OPTS = -O2 -Wall -pedantic -Wno-deprecated-declarations 15 | POST = strip mm$(E) 16 | endif 17 | 18 | # PREFIX is the base directory under which to install the binary and man 19 | # page; generally either /usr/local or /usr (or perhaps /opt...). 20 | 21 | PREFIX = /usr/local 22 | 23 | #-------------------------------------------------------------- 24 | # Defaults are for the standard curses setup: 25 | 26 | # CURS_DIR specifies the directory with curses.h, if it's not in the 27 | # include path. LIBS lists any "extra" libraries that need to be linked 28 | # in, including the curses library. RM is the Delete command ("rm" or 29 | # "del", as appropriate), and SEP is the separator for multi-statement 30 | # lines... some systems require ";", while others need "&&". 31 | 32 | ifeq ($(OS),Windows_NT) 33 | CURS_DIR = /pdcurses 34 | LIBS = $(CURS_DIR)/wincon/pdcurses.a 35 | RM = del 36 | SEP = && 37 | E = .exe 38 | BUILD = $(CXX) -static 39 | else 40 | CURS_DIR = . 41 | LIBS = -lcurses 42 | RM = rm -f 43 | SEP = ; 44 | E = 45 | BUILD = $(CXX) 46 | endif 47 | 48 | #-------------------------------------------------------------- 49 | # With PDCurses for X11: 50 | 51 | ifeq ($(SYS),X11) 52 | CURS_DIR = $(shell xcurses-config --cflags | cut -c 3-) 53 | LIBS = $(shell xcurses-config --libs) 54 | endif 55 | 56 | #-------------------------------------------------------------- 57 | # With PDCurses for SDL: 58 | 59 | ifeq ($(SYS),SDL) 60 | CURS_DIR = /Users/wmcbrine/pdsrc/PDCurses 61 | LIBS = $(CURS_DIR)/sdl2/pdcurses.a $(shell sdl2-config --libs) 62 | endif 63 | 64 | #-------------------------------------------------------------- 65 | # For DJGPP: 66 | 67 | ifeq ($(SYS),DOS) 68 | CURS_DIR = /pdcurses 69 | LIBS = $(CURS_DIR)/dos/pdcurses.a 70 | RM = del 71 | SEP = ; 72 | E = .exe 73 | POST = strip mm.exe; exe2coff mm.exe; copy /b \ 74 | c:\djgpp\bin\cwsdstub.exe+mm mm.exe; del mm 75 | endif 76 | 77 | HELPDIR = $(PREFIX)/man/man1 78 | O = o 79 | 80 | .SUFFIXES: .cc 81 | .PHONY: clean dep install 82 | 83 | all: mm$(E) mm.1 84 | 85 | MOBJS = misc.o resource.o mmail.o filelist.o netadd.o area.o letter.o \ 86 | read.o compress.o pktbase.o bw.o qwk.o omen.o soup.o opx.o 87 | 88 | IOBJS = mmcolor.o mysystem.o isoconv.o basic.o interfac.o packet.o \ 89 | arealist.o letterl.o letterw.o lettpost.o ansiview.o addrbook.o \ 90 | tagline.o help.o main.o 91 | 92 | $(MOBJS) : %.o: $(msrc)/%.cc 93 | $(CXX) $(OPTS) -c $< 94 | 95 | $(IOBJS) : %.o: $(isrc)/%.cc 96 | $(CXX) $(OPTS) -I$(CURS_DIR) -c $< 97 | 98 | mm$(E): $(MOBJS) $(IOBJS) 99 | $(BUILD) -o mm$(E) $(MOBJS) $(IOBJS) $(LIBS) 100 | $(POST) 101 | 102 | mm.1: MANUAL.md 103 | md2man-roff MANUAL.md > mm.1 104 | 105 | dep: 106 | $(CXX) -MM $(msrc)/*.cc | sed s/"\.o"/"\.\$$(O)"/ > depend 107 | $(CXX) -I$(CURS_DIR) -MM $(isrc)/*.cc | sed s/"\.o"/"\.\$$(O)"/ >> depend 108 | 109 | clean: 110 | $(RM) *.o 111 | $(RM) mm$(E) 112 | 113 | install:: 114 | install -c -s mm $(PREFIX)/bin 115 | install -c -m 644 mm.1 $(HELPDIR) 116 | 117 | include depend 118 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | MultiMail compilation and installation procedure 2 | ================================================ 3 | 4 | These instructions assume that you're compiling MultiMail from source. For 5 | precompiled binaries, see the README files that accompany them instead. 6 | 7 | 1. Make sure any needed packages are installed -- 8 | In addition to the MultiMail package itself, you'll also need InfoZip 9 | or PKZIP (and/or LHA, ARJ, etc.) to uncompress the packets and 10 | compress the replies. InfoZip is available from: 11 | 12 | 13 | 14 | (PKZIP is the default for DOS; InfoZip is the default for other 15 | platforms.) The programs should be installed somewhere in the PATH; 16 | otherwise, the full path must be specified in ~/.mmailrc. 17 | 18 | To compile MultiMail, you'll need curses -- either ncurses, SysV 19 | curses (e.g., Solaris curses), or PDCurses. You can get ncurses from: 20 | 21 | 22 | 23 | PDCurses is available at: 24 | 25 | 26 | 27 | (If you're using Linux, you probably already have ncurses and 28 | InfoZip.) 29 | 30 | If using PDCurses, MultiMail now requires version 3.6 or later. 31 | 32 | The 16-bit MS-DOS Turbo C++ port also uses Ralf Brown's SPAWNO 33 | library: 34 | 35 | 36 | 37 | 2. Configure it (for compilation) -- 38 | Check the options and paths in the Makefile. If curses.h isn't in 39 | the include path, change CURS_DIR as appropriate. You may also need 40 | to change LIBS. These can be set on the command line, e.g. "make 41 | CURS_DIR=/pdcurses". 42 | 43 | 3. Compile MultiMail -- 44 | At the base directory, type: `make` 45 | 46 | 4. Run it -- 47 | Type: `./mm` 48 | (For DOS, OS/2 or Windows, set the MMAIL or HOME variable, then run mm.) 49 | 50 | 5. (Optional:) Configure it (for end user) -- 51 | Edit the ~/.mmailrc file. (For DOS, OS/2 or Windows, mmail.rc.) 52 | 53 | 6. (Optional:) Install it system-wide -- 54 | Type: `make install` 55 | to install the manual and binary under /usr/local 56 | (requires root access). (This doesn't work in DOS, OS/2 or Windows.) 57 | 58 | See the [man page](MANUAL.md) and [README.md] for more information. 59 | 60 | This package includes some example color schemes, in the "colors" 61 | directory. To select one, use the "ColorFile" keyword in .mmailrc . 62 | 63 | 64 | Support for XCurses (PDCurses) 65 | ------------------------------ 66 | 67 | When MultiMail is compiled with XCurses, you can use the X resource 68 | database to set certain startup options. Here are some example resources: 69 | 70 | XCurses*normalFont: 9x15 71 | XCurses*boldFont: 9x15bold 72 | XCurses*lines: 30 73 | XCurses*cols: 80 74 | 75 | For details, see the PDCurses documentation. 76 | 77 | If you're using a non-X text editor with an XCurses version of MultiMail, 78 | it will work better if you set MultiMail's editor variable to "xterm -e 79 | $EDITOR" instead of just "$EDITOR" (the default). 80 | 81 | 82 | Compile notes: Windows, MS-DOS, and OS/2 83 | ---------------------------------------- 84 | 85 | In the MultiMail source, separate makefiles are provided for these ports. 86 | 87 | Makefile - GCC (including DJGPP and MinGW) 88 | Makefile.bcc - Borland C++ (Windows, MS-DOS) 89 | Makefile.vc - Microsoft Visual C++ (Windows) 90 | Makefile.wcc - Watcom (All platforms -- Windows by default) 91 | 92 | Point to your installation of PDCurses and compile with, e.g.: 93 | 94 | make -f Makefile.bcc CURS_DIR=/pdcurs38 SYS=DOS 95 | 96 | (Use "wmake" instead of "make" for Watcom; "nmake" for MSVC.) 97 | -------------------------------------------------------------------------------- /colors/COLORS.md: -------------------------------------------------------------------------------- 1 | Color files for MultiMail 2 | ========================= 3 | 4 | The color file lets you specify the colors used by MultiMail (on color 5 | terminals). When you run MultiMail for the first time, the file "colors" 6 | will be automatically generated in the "mmail" directory, listing the 7 | default colors. 8 | 9 | The format for each line is "ItemName: \, \, 10 | \", where foreground and background are one of Black, Blue, 11 | Green, Cyan, Red, Magenta, Yellow, or White, and the attribute may be Bold 12 | or Reverse. Only the first three letters are actually checked, and case 13 | is not signifigant. 14 | 15 | If no color for a given ItemName is defined, the default will be used. 16 | Lines beginning with '#' are commented out. 17 | 18 | "Bold" is mainly used to indicate a bright foreground color, but may also 19 | (or alternatively) cause actual boldfacing on some terminals (like xterm). 20 | "Reverse" is for the benefit of monochrome terminals (xterm again), so 21 | that highlights still show up there. (Yes, color schemes are fairly 22 | useless on a monochrome terminal; but with care, this feature lets you set 23 | up the color file once, so you can use MultiMail from both color and mono 24 | terminals without having to change anything.) 25 | 26 | You can omit the foreground and background colors, as well as the 27 | attribute. (If only one color is specified, it's taken as the foreground 28 | color.) If omitted, the last-defined foreground or background color is 29 | used. (Note that this does NOT work for the attribute, which is reset with 30 | each color. So, if you have an item with the Bold attribute, and want the 31 | next item to be exactly the same, you still have to specify "Bold".) I did 32 | this because color schemes tend to have a lot of repeated values, 33 | especially for the background color. See the included color schemes for 34 | examples. 35 | 36 | Also note that an ItemName with no values after it is NOT the same as an 37 | ItemName that's commented out or removed. In the first case, the colors 38 | will default to those defined for the previous item. In the second case, 39 | the colors used will be the default colors for that item (as found in 40 | tradit.col, or the autogenerated "colors" file). 41 | 42 | You can switch between color schemes by changing the "ColorFile" keyword 43 | in your .mmailrc to point to a new file. The example color files included 44 | here are: 45 | 46 | - aqua.col - This is the first color scheme I've made for myself, and 47 | it's the one I'm using now. It's based on blue and cyan, 48 | with white backgrounds for black text, and just a hint of 49 | red. :-) 50 | 51 | - gilmore.col - Donated by Gary Gilmore. 52 | 53 | - holger2.col - Donated by Holger Granholm. 54 | 55 | - ingo.col - Approximately the color scheme used by Ingo Brueckl in 56 | his own version of MultiMail. It resembles the version 57 | 0.1 color scheme. 58 | 59 | - tonys.col - Donated by Tony Summerfelt. 60 | 61 | - toutant.col - Donated by David Toutant. 62 | 63 | - tradit.col - The standard color scheme used in versions 0.2 through 64 | 0.37, based on the version 0.1 scheme, with signifigant 65 | alterations by John Zero. 66 | 67 | - tuukka.col - A monochrome scheme, donated by Tuukka Lehtinen. 68 | 69 | - ver01.col - Approximately, the original color scheme found in the 70 | version 0.1 series, as devised by Kolossvary Tamas. (Some 71 | things don't quite translate, due to differences in the 72 | screen layout.) 73 | 74 | I welcome additonal color schemes. If you have an interesting one, please 75 | send it to . 76 | -------------------------------------------------------------------------------- /mmail/compress.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * compress and decompress packets 4 | 5 | Copyright 1997 John Zero 6 | Copyright 1998-2021 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #include "compress.h" 10 | 11 | enum atype {A_ARJ, A_ZIP, A_LHA, A_RAR, A_TAR, A_UNKNOWN, A_UNEXIST}; 12 | 13 | atype lastAType = A_UNKNOWN; // saves last atype for compress routine 14 | 15 | atype getArchiveType(const char *fname) 16 | { 17 | FILE *f; 18 | unsigned magic; 19 | atype tip = A_UNKNOWN; 20 | 21 | f = fopen(fname, "rb"); 22 | if (!f) 23 | return A_UNEXIST; 24 | magic = fgetc(f) << 8; 25 | magic += fgetc(f); 26 | 27 | switch (magic) { 28 | case 0x60EA: 29 | tip = A_ARJ; 30 | break; 31 | case 0x1F8B: 32 | tip = A_TAR; // actually the GZIP signature 33 | break; 34 | case 0x504B: // PK - check for ZIP 35 | if (3 == fgetc(f)) 36 | if (4 == fgetc(f)) 37 | tip = A_ZIP; 38 | break; 39 | case 0x5261: // Ra - check for RAR 40 | if ('r' == fgetc(f)) 41 | if ('!' == fgetc(f)) 42 | tip = A_RAR; 43 | break; 44 | default: // can be LHA - check 3. and 4. bytes 45 | if ('-' == fgetc(f)) 46 | if ('l' == fgetc(f)) 47 | tip = A_LHA; 48 | // we should check another byte (l/z) but i'm lazy 49 | } 50 | fclose(f); 51 | 52 | return tip; 53 | } 54 | 55 | // clears the working directory and uncompresses the packet into it. 56 | pktstatus uncompressFile(const char *fname, const char *todir, bool setAType) 57 | { 58 | static const int uncstr[] = { 59 | arjUncompressCommand, zipUncompressCommand, 60 | lhaUncompressCommand, rarUncompressCommand, 61 | tarUncompressCommand, unknownUncompressCommand 62 | }; 63 | clearDirectory(todir); 64 | 65 | atype at = getArchiveType(fname); 66 | if (at == A_UNEXIST) 67 | return PKT_UNFOUND; 68 | if (setAType) 69 | lastAType = at; 70 | 71 | return mysystem2(mm.res.get(uncstr[at]), fname) ? UNCOMP_FAIL : PKT_OK; 72 | } 73 | 74 | int compressAddFile(const char *arcdir, const char *arcfile, 75 | const char *addfname) 76 | { 77 | static const int cmpstr[] = { 78 | arjCompressCommand, zipCompressCommand, 79 | lhaCompressCommand, rarCompressCommand, 80 | tarCompressCommand, unknownCompressCommand 81 | }; 82 | int result; 83 | 84 | char *filepath = fullpath(arcdir, arcfile); 85 | 86 | mystat st(filepath); 87 | 88 | #ifdef TAR_KLUDGE 89 | // For tar files, forget the parameter passed -- just archive 90 | // everything. (Adding a single file would be a multi-step 91 | // process. You could do it via a script.) 92 | 93 | if (lastAType == A_TAR) 94 | addfname = "*"; 95 | #endif 96 | if (!st.readable() || st.writeable()) { 97 | const char *cm = mm.res.get(cmpstr[lastAType]); 98 | char *qname = canonize(quotespace(filepath)); 99 | char *cmdline = new char[strlen(qname) + strlen(cm) + 100 | strlen(addfname) + 6]; 101 | sprintf(cmdline, "%s %s %s", cm, qname, addfname); 102 | 103 | result = mysystem(cmdline); 104 | 105 | st.reset_date(filepath); 106 | 107 | if (lastAType == A_LHA) { // then the fixup 108 | strcpy(filepath, arcfile); 109 | strtok(filepath, "."); 110 | sprintf(cmdline, "%s/%s.bak", arcdir, filepath); 111 | remove(cmdline); 112 | } 113 | 114 | delete[] cmdline; 115 | delete[] qname; 116 | } else 117 | result = -1; 118 | 119 | delete[] filepath; 120 | 121 | return result; 122 | } 123 | 124 | // mainly for use with OMEN replies 125 | const char *defExtent() 126 | { 127 | static const char *ext[] = {"arj", "zip", "lzh", "rar", "tgz", ""}; 128 | 129 | return ext[lastAType]; 130 | } 131 | -------------------------------------------------------------------------------- /mmail/pktbase.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * Packet base class 4 | 5 | Copyright 1999-2021 William McBrine 6 | Distributed under the GNU General Public License, version 3 or later. */ 7 | 8 | #ifndef PKT_H 9 | #define PKT_H 10 | 11 | #include "mmail.h" 12 | 13 | class pktbase : public specific_driver 14 | { 15 | protected: 16 | struct bodytype { 17 | long pointer, msgLength; 18 | } **body; 19 | 20 | struct AREAs { 21 | char *name; 22 | int num, nummsgs; 23 | unsigned long attr; 24 | char numA[10]; // padded to deal with alignment bug (EMX) 25 | } *areas; 26 | 27 | struct ndx_fake { 28 | int confnum; 29 | long pointer, length; 30 | bool pers; 31 | ndx_fake *next; 32 | }; 33 | 34 | letter_body *bodyString; 35 | file_header **bulletins; 36 | 37 | FILE *infile; 38 | char packetBaseName[9]; 39 | char *LoginName, *AliasName, *BBSName, *SysOpName, *DoorProg, *BBSProg; 40 | char *hello, *goodbye; 41 | int maxConf, numMsgs, ID, currentArea, currentLetter; 42 | unsigned long hasOffConfig; 43 | bool hasPers; 44 | 45 | void cleanup(); 46 | void initBody(ndx_fake *, int); 47 | int getYNum(int, unsigned long); 48 | void checkLatin(letter_header &); 49 | const char *getHidden(const char *, char *&); 50 | void fidocheck(letter_header &); 51 | void listBulletins(const char [][13], int, int = 2); 52 | char *nextLine(); 53 | 54 | virtual void prefirstblk(); 55 | virtual void getblk(int, long &, long, unsigned char *&, unsigned char *&); 56 | virtual void postfirstblk(unsigned char *&, letter_header &); 57 | virtual void endproc(letter_header &); 58 | public: 59 | pktbase(); 60 | ~pktbase(); 61 | int getXNum(int); 62 | int getNoOfAreas(); 63 | virtual int getNoOfLetters(); 64 | void selectArea(int); 65 | void resetLetters(); 66 | bool hasPersArea(); 67 | virtual bool hasPersonal(); 68 | virtual bool isLatin(); 69 | virtual const char *oldFlagsName(); 70 | virtual bool readOldFlags(); 71 | virtual bool saveOldFlags(); 72 | virtual letter_body *getBody(letter_header &); 73 | const char *getLoginName(); 74 | const char *getAliasName(); 75 | const char *getBBSName(); 76 | const char *getSysOpName(); 77 | const char *getBBSProg(); 78 | const char *getDoorProg(); 79 | file_header *getHello(); 80 | file_header *getGoodbye(); 81 | file_header **getBulletins(); 82 | virtual const char *getTear(int); 83 | const char *getBaseName(); 84 | }; 85 | 86 | class pktreply : public reply_driver 87 | { 88 | protected: 89 | class upl_base { 90 | public: 91 | char *fname; 92 | upl_base *nextRecord; 93 | long msglen; 94 | 95 | upl_base(const char *); 96 | ~upl_base(); 97 | } *uplListHead, *uplListCurrent; 98 | 99 | file_list *upWorkList; 100 | letter_body *replyText; 101 | 102 | char replyPacketName[13], replyInnerName[13]; 103 | int currentLetter, noOfLetters; 104 | bool replyExists; 105 | 106 | void uncompress(); 107 | virtual void getReplies(FILE *) = 0; 108 | void readRep(); 109 | virtual void repFileName() = 0; 110 | void addUpl(upl_base *); 111 | virtual void addRep1(FILE *, upl_base *, int) = 0; 112 | virtual void addHeader(FILE *) = 0; 113 | virtual const char *repTemplate(bool) = 0; 114 | public: 115 | pktreply(); 116 | ~pktreply(); 117 | bool checkForReplies(); 118 | void init(); 119 | int getNoOfAreas(); 120 | void selectArea(int); 121 | int getNoOfLetters(); 122 | void resetLetters(); 123 | letter_body *getBody(letter_header &); 124 | bool hasPersArea(); 125 | bool hasPersonal(); 126 | bool isLatin(); 127 | const char *oldFlagsName(); 128 | bool readOldFlags(); 129 | bool saveOldFlags(); 130 | const char *getLoginName(); 131 | const char *getAliasName(); 132 | const char *getBBSName(); 133 | const char *getSysOpName(); 134 | const char *getBBSProg(); 135 | const char *getDoorProg(); 136 | file_header *getHello(); 137 | file_header *getGoodbye(); 138 | file_header *getFileList(); 139 | file_header **getBulletins(); 140 | const char *getTear(int); 141 | const char *getBaseName(); 142 | void killLetter(int); 143 | area_header *refreshArea(); 144 | bool makeReply(); 145 | void deleteReplies(); 146 | }; 147 | 148 | #endif 149 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #define STRingize(x) #x 2 | #define STR(x) STRingize(x) 3 | 4 | #define MM_MAJOR 0 5 | #define MM_MINOR 52 6 | #define MM_YEAR "2021" 7 | 8 | #define MM_NAME "MultiMail" 9 | #define MM_SNAME "MMail" 10 | #define MM_VERNUM STR(MM_MAJOR) "." STR(MM_MINOR) 11 | #define MM_TOPHEADER MM_NAME "/%.16s v" MM_VERNUM 12 | 13 | #define USE_SHADOWS // "Shadowed" windows 14 | #define VANITY_PLATE // Author info -- undefine for longer packet list 15 | 16 | /* With the default tar/gz compress command, leave this on. It causes the 17 | entire file to be rearchived when using that format, instead of just 18 | the .red file. If you replace the tar/gz command with something 19 | smarter (e.g., via a script), you can disable the kludge. 20 | */ 21 | #define TAR_KLUDGE 22 | 23 | /* ----- Some supported compilers ----- */ 24 | 25 | #if defined(__TURBOC__) && defined(__MSDOS__) 26 | # define TURBO16 27 | #endif 28 | 29 | #if defined(__TURBOC__) && defined(__WIN32__) 30 | # define BORLAND32 31 | #endif 32 | 33 | #if defined(__WATCOMC__) && defined(__I86__) 34 | # define WATCOM16 35 | #endif 36 | 37 | #if defined(TURBO16) || defined(WATCOM16) 38 | # define SIXTEENBIT 39 | #endif 40 | 41 | /* ----- Platform-specific defines ----- */ 42 | 43 | /* HAS_UNAME determines whether the "System" part of the tearline is taken 44 | from the uname() function, or is hardwired. Turbo/Borland C++ and 45 | Watcom don't have it. 46 | */ 47 | #if !defined(__TURBOC__) && !defined(__MINGW32__) && !defined(__WATCOMC__) && !defined(_MSC_VER) 48 | # define HAS_UNAME 49 | #endif 50 | 51 | /* MS-DOS and DOS-like systems. */ 52 | 53 | #if defined(__MSDOS__) || defined(__WIN32__) || defined(__OS2__) 54 | 55 | # define DOSCHARS 56 | # define DOSNAMES 57 | # define USE_SHELL 58 | # define EXTRAPATH 59 | 60 | /* Assume the use of PDCurses on these platforms (can't check it 61 | explicitly until curses.h is included), and so: 62 | 63 | Some characters are unprintable on standard Unix terminals, even with 64 | A_ALTCHARSET set. But PDCurses will handle them. 65 | */ 66 | # define ALLCHARSOK 67 | 68 | #else 69 | 70 | /* Not a DOS-like system -- enable home directory elision. 71 | */ 72 | 73 | # define HAS_HOME 74 | 75 | #endif 76 | 77 | /* Also, see the NCURSES_SIGWINCH definition in interfac/interfac.h -- it 78 | should be fine as is, but may need manual adjustment in some cases. 79 | */ 80 | 81 | /* I use strcasecmp() and strncasecmp() throughout, but some systems call 82 | these functions stricmp() and strincmp(). 83 | */ 84 | #if defined(__TURBOC__) || defined(__WATCOMC__) || defined(_MSC_VER) 85 | # define USE_STRICMP 86 | #endif 87 | 88 | /* unistd.h is the POSIX header file. Borland/Turbo C doesn't have it. */ 89 | 90 | #if !defined(__TURBOC__) && !defined(__MINGW32__) && !defined(__WATCOMC__) && !defined(_MSC_VER) 91 | # define HAS_UNISTD 92 | #endif 93 | 94 | /* Limit allocation sizes for 16-bit systems */ 95 | 96 | #ifdef SIXTEENBIT 97 | # define LIMIT_MEM 98 | # define MAXBLOCK 0x0FFE0L 99 | #else 100 | # define MAXBLOCK 0x07FFFFFE0L 101 | #endif 102 | 103 | /* For the 16-bit MS-DOS version compiled with Turbo C++ 3.0, I've added 104 | the SPAWNO library by Ralf Brown to get more memory when shelling. 105 | */ 106 | #ifdef TURBO16 107 | # define USE_SPAWNO 108 | #endif 109 | 110 | /* Watcom (all DOSish platforms) and Turbo C++ need an extra call to get 111 | the drive letter changed, when changing directories. 112 | */ 113 | #if defined(__WATCOMC__) || defined(TURBO16) 114 | # define USE_SETDISK 115 | #endif 116 | 117 | /* In Borland/Turbo C++, using findfirst()/findnext() is faster than 118 | using readdir()/stat(). 119 | */ 120 | #ifdef __TURBOC__ 121 | # define USE_DIRH 122 | # define USE_FINDFIRST 123 | #endif 124 | 125 | /* Another variation, _findfirst()/_findnext(), for Windows. */ 126 | 127 | #ifdef __WIN32__ 128 | # ifndef USE_FINDFIRST 129 | # define USE_FINDFIRST 130 | # endif 131 | # define USE_IOH 132 | #endif 133 | 134 | /* Borland C++ 5.5 barfs on time_t = 0, which appears as the timestamp of 135 | the top-level directory. Also, utime(), though implemented, doesn't work 136 | right. 137 | */ 138 | #ifdef BORLAND32 139 | # define TIMEKLUDGE 140 | # define USE_SETFTIME 141 | #endif 142 | 143 | /* Turbo C++ 3.0 lacks the "bool" and "off_t" types.*/ 144 | 145 | #ifndef TURBO16 146 | # define HAS_BOOL 147 | # define HAS_OFFT 148 | #endif 149 | 150 | /* Some lines in the code serve no purpose but to suppress the GCC warning 151 | "might be used uninitialized in this function". Borland C++ 5.5, on the 152 | other hand, complains "is assigned a value that is never used" if I 153 | leave these lines _in_. 154 | */ 155 | #ifndef __TURBOC__ 156 | # define BOGUS_WARNING 157 | #endif 158 | 159 | /* ----- End of platform-specific defines ----- */ 160 | 161 | #ifndef HAS_BOOL 162 | typedef unsigned char bool; 163 | # define true 1 164 | # define false 0 165 | #endif 166 | 167 | #ifndef HAS_OFFT 168 | typedef long off_t; 169 | #endif 170 | -------------------------------------------------------------------------------- /mmail/mmail.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * mmail class 4 | 5 | Copyright 1996 Toth Istvan 6 | Copyright 1998-2021 William McBrine , 7 | Robert Vukovic 8 | Distributed under the GNU General Public License, version 3 or later. */ 9 | 10 | #include "mmail.h" 11 | #include "compress.h" 12 | #include "../interfac/error.h" 13 | 14 | #include "bw.h" 15 | #include "qwk.h" 16 | #include "omen.h" 17 | #include "soup.h" 18 | #include "opx.h" 19 | 20 | enum pktype {PKT_QWK, PKT_BW, PKT_OMEN, PKT_SOUP, PKT_OPX, PKT_UNDEF}; 21 | 22 | void mmail::detect_and_open() 23 | { 24 | pktype mode; 25 | 26 | synchro = false; 27 | 28 | if (workList->exists("control.dat") && workList->exists("messages.dat")) 29 | mode = PKT_QWK; 30 | else 31 | if (workList->exists(".inf")) 32 | mode = PKT_BW; 33 | else 34 | if (workList->exists("brdinfo.dat")) 35 | mode = PKT_OPX; 36 | else 37 | if (workList->exists("areas")) 38 | mode = PKT_SOUP; 39 | else 40 | if (workList->exists("system")) 41 | mode = PKT_OMEN; 42 | else 43 | mode = PKT_UNDEF; 44 | 45 | switch (mode) { 46 | case PKT_BW: 47 | packet = new bluewave(); 48 | reply = new bwreply(); 49 | break; 50 | case PKT_QWK: 51 | packet = new qwkpack(); 52 | reply = new qwkreply(); 53 | break; 54 | case PKT_OMEN: 55 | packet = new omen(); 56 | reply = new omenrep(); 57 | break; 58 | case PKT_SOUP: 59 | packet = new soup(); 60 | reply = new souprep(); 61 | break; 62 | case PKT_OPX: 63 | packet = new opxpack(); 64 | reply = new opxreply(); 65 | break; 66 | default: 67 | packet = 0; 68 | reply = 0; 69 | } 70 | 71 | if (mode != PKT_UNDEF) { 72 | packet_read = new main_read_class(packet); 73 | reply_read = new reply_read_class(reply); 74 | } else { 75 | packet_read = 0; 76 | reply_read = 0; 77 | } 78 | } 79 | 80 | void mmail::Delete() 81 | { 82 | if (packet) { 83 | delete reply_read; 84 | delete packet_read; 85 | delete reply; 86 | delete packet; 87 | } 88 | 89 | delete areaList; 90 | delete workList; 91 | } 92 | 93 | // Open a packet 94 | pktstatus mmail::selectPacket(const char *packetName) 95 | { 96 | pktstatus result; 97 | 98 | const char *x = strrchr(packetName, '/'); 99 | if (!x) 100 | x = strrchr(packetName, '\\'); 101 | if (x) { 102 | size_t len = x - packetName; 103 | char *fname = new char[len + 1]; 104 | strnzcpy(fname, packetName, len); 105 | 106 | mychdir(error.getOrigDir()); 107 | mychdir(fname); 108 | delete[] fname; 109 | 110 | fname = mygetcwd(); 111 | res.set_noalloc(PacketDir, fname); 112 | packetName = x + 1; 113 | } 114 | res.set(PacketName, packetName); 115 | 116 | // Uncompression is done here 117 | char *fpath = fullpath(res.get(PacketDir), packetName); 118 | 119 | if (!res.get(oldPacketName) || 120 | strcmp(packetName, res.get(oldPacketName))) { 121 | 122 | res.set(oldPacketName, packetName); 123 | result = uncompressFile(fpath, res.get(WorkDir), true); 124 | if (result != PKT_OK) 125 | return result; 126 | } 127 | 128 | delete[] fpath; 129 | 130 | workList = new file_list(res.get(WorkDir)); 131 | 132 | if (!workList->getNoOfFiles()) { 133 | delete workList; 134 | return PKT_NOFILES; 135 | } 136 | 137 | detect_and_open(); 138 | 139 | if (!packet) { 140 | delete workList; 141 | return PTYPE_UNK; 142 | } 143 | return PKT_OK; 144 | } 145 | 146 | // Save last read pointers 147 | bool mmail::saveRead() 148 | { 149 | return mm.getReadObject(packet)->saveAll(); 150 | } 151 | 152 | // Is there a reply packet? 153 | bool mmail::checkForReplies() 154 | { 155 | return reply->checkForReplies(); 156 | } 157 | 158 | // Create a reply packet 159 | bool mmail::makeReply() 160 | { 161 | return reply->makeReply(); 162 | } 163 | 164 | void mmail::deleteReplies() 165 | { 166 | reply->deleteReplies(); 167 | 168 | // to reset the "replyExists" flag (inelegant, I know): 169 | checkForReplies(); 170 | } 171 | 172 | void mmail::openReply() 173 | { 174 | reply->init(); 175 | } 176 | 177 | bool mmail::getOffConfig() 178 | { 179 | return reply->getOffConfig(); 180 | } 181 | 182 | void mmail::initRead() 183 | { 184 | if (reply_read) 185 | reply_read->init(); 186 | if (packet_read) 187 | packet_read->init(); 188 | } 189 | 190 | specific_driver *mmail::getDriver(int areaNo) 191 | { 192 | return (areaNo != REPLY_AREA) ? packet : reply; 193 | } 194 | 195 | read_class *mmail::getReadObject(specific_driver *driver) 196 | { 197 | return (driver == packet) ? packet_read : reply_read; 198 | } 199 | 200 | int mmail::getOffset(specific_driver *driver) 201 | { 202 | return (driver == packet); 203 | } 204 | -------------------------------------------------------------------------------- /mmail/read.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * main_read_class, reply_read_class 4 | 5 | Copyright 1996-1997 Toth Istvan 6 | Copyright 1997-2021 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #include "mmail.h" 10 | #include "compress.h" 11 | 12 | /* read_class -- virtual */ 13 | 14 | read_class::~read_class() 15 | { 16 | } 17 | 18 | /* main_read_class -- for regular areas */ 19 | 20 | main_read_class::main_read_class(specific_driver *driverA) : driver(driverA) 21 | { 22 | noOfAreas = driver->getNoOfAreas(); 23 | noOfLetters = new int[noOfAreas]; 24 | readStore = new int *[noOfAreas]; 25 | 26 | for (int c = 0; c < noOfAreas; c++) { 27 | driver->selectArea(c); 28 | 29 | int numlett = driver->getNoOfLetters(); 30 | noOfLetters[c] = numlett; 31 | readStore[c] = numlett ? new int[numlett] : 0; 32 | 33 | for (int d = 0; d < numlett; d++) 34 | readStore[c][d] = 0; 35 | } 36 | 37 | hasPersArea = driver->hasPersArea(); 38 | hasPersNdx = !(!(mm.workList->exists("personal.ndx"))); 39 | } 40 | 41 | main_read_class::~main_read_class() 42 | { 43 | while(noOfAreas--) 44 | delete[] readStore[noOfAreas]; 45 | delete[] readStore; 46 | delete[] noOfLetters; 47 | } 48 | 49 | void main_read_class::init() 50 | { 51 | // If basename.red not found, look for any .red file; 52 | // then look for an old-style file, and use the most recent: 53 | 54 | file_header *redfile, *oldfile; 55 | file_list *wl = mm.workList; 56 | 57 | redfile = wl->existsF(readFilePath(mm.res.get(PacketName))); 58 | if (!redfile) 59 | redfile = wl->existsF(".red"); 60 | 61 | const char *oldFileN = driver->oldFlagsName(); 62 | oldfile = oldFileN ? wl->existsF(oldFileN) : 0; 63 | 64 | bool oldused = (oldfile && (!redfile || (oldfile->getDate() > 65 | redfile->getDate()))); 66 | 67 | if (oldused) { 68 | int oldsort = lsorttype; 69 | lsorttype = LS_MSGNUM; 70 | 71 | oldused = driver->readOldFlags(); 72 | 73 | lsorttype = oldsort; 74 | } 75 | 76 | if (!oldused) { 77 | FILE *readFile; 78 | const char *readFileN = redfile ? redfile->getName() : 0; 79 | 80 | readFile = readFileN ? wl->ftryopen(readFileN) : 0; 81 | 82 | if (readFile) { 83 | // Don't init personal area, unless using QWK personal.ndx 84 | // (this is for backwards compatibility): 85 | 86 | int skip = hasPersArea && !hasPersNdx; 87 | 88 | for (int c = skip; c < noOfAreas; c++) 89 | for (int d = 0; d < noOfLetters[c]; d++) 90 | setStatus(c, d, fgetc(readFile)); 91 | 92 | fclose(readFile); 93 | } 94 | } 95 | } 96 | 97 | void main_read_class::setRead(int area, int letter, bool value) 98 | { 99 | int flag = getStatus(area, letter); 100 | 101 | if (value) 102 | flag |= MS_READ; 103 | else 104 | flag &= ~MS_READ; 105 | 106 | setStatus(area, letter, flag); 107 | } 108 | 109 | bool main_read_class::getRead(int area, int letter) 110 | { 111 | return !(!(getStatus(area, letter) & MS_READ)); 112 | } 113 | 114 | void main_read_class::setStatus(int area, int letter, int value) 115 | { 116 | int *areaP = readStore[area]; 117 | if (areaP) 118 | areaP[letter] = value; 119 | } 120 | 121 | int main_read_class::getStatus(int area, int letter) 122 | { 123 | int *areaP = readStore[area]; 124 | return areaP ? areaP[letter] : 0; 125 | } 126 | 127 | int main_read_class::getNoOfUnread(int area) 128 | { 129 | int tmp = 0; 130 | 131 | for (int c = 0; c < noOfLetters[area]; c++) 132 | if (!(getStatus(area, c) & MS_READ)) 133 | tmp++; 134 | return tmp; 135 | } 136 | 137 | int main_read_class::getNoOfMarked(int area) 138 | { 139 | int tmp = 0; 140 | 141 | for (int c = 0; c < noOfLetters[area]; c++) 142 | if (getStatus(area, c) & MS_MARKED) 143 | tmp++; 144 | return tmp; 145 | } 146 | 147 | bool main_read_class::saveAll() 148 | { 149 | const char *readFileN = 0, *oldFileN = driver->oldFlagsName(); 150 | 151 | bool oldused = !(!oldFileN); 152 | 153 | if (mychdir(mm.res.get(WorkDir))) 154 | fatalError("Unable to change to work directory"); 155 | 156 | if (oldused) { 157 | int oldsort = lsorttype; 158 | lsorttype = LS_MSGNUM; 159 | 160 | oldused = driver->saveOldFlags(); 161 | 162 | lsorttype = oldsort; 163 | } 164 | 165 | if (!oldused) { 166 | FILE *readFile; 167 | 168 | readFileN = readFilePath(mm.res.get(PacketName)); 169 | readFile = fopen(readFileN, "wb"); 170 | 171 | for (int c = (hasPersArea && !hasPersNdx); c < noOfAreas; c++) 172 | for (int d = 0; d < noOfLetters[c]; d++) 173 | fputc(getStatus(c, d), readFile); 174 | 175 | fclose(readFile); 176 | } 177 | 178 | // add the .red file to the packet 179 | return !compressAddFile(mm.res.get(PacketDir), mm.res.get(PacketName), 180 | oldFileN ? oldFileN : readFileN); 181 | } 182 | 183 | const char *main_read_class::readFilePath(const char *FileN) 184 | { 185 | static char tmp[13]; 186 | 187 | sprintf(tmp, "%.8s.red", findBaseName(FileN)); 188 | return tmp; 189 | } 190 | 191 | /* reply_read_class -- for reply areas */ 192 | /* (Formerly known as dummy_read_class, because it does almost nothing) */ 193 | 194 | reply_read_class::reply_read_class(specific_driver *) 195 | { 196 | } 197 | 198 | reply_read_class::~reply_read_class() 199 | { 200 | } 201 | 202 | void reply_read_class::init() 203 | { 204 | } 205 | 206 | void reply_read_class::setRead(int, int, bool) 207 | { 208 | } 209 | 210 | bool reply_read_class::getRead(int, int) 211 | { 212 | return true; 213 | } 214 | 215 | void reply_read_class::setStatus(int, int, int) 216 | { 217 | } 218 | 219 | int reply_read_class::getStatus(int, int) 220 | { 221 | return 1; 222 | } 223 | 224 | int reply_read_class::getNoOfUnread(int) 225 | { 226 | return 0; 227 | } 228 | 229 | int reply_read_class::getNoOfMarked(int) 230 | { 231 | return 0; 232 | } 233 | 234 | bool reply_read_class::saveAll() 235 | { 236 | return true; 237 | } 238 | -------------------------------------------------------------------------------- /depend: -------------------------------------------------------------------------------- 1 | area.$(O): mmail/area.cc mmail/mmail.h mmail/../config.h \ 2 | mmail/../mmail/misc.h mmail/../mmail/resource.h \ 3 | mmail/../interfac/mysystem.h 4 | bw.$(O): mmail/bw.cc mmail/bw.h mmail/pktbase.h mmail/mmail.h \ 5 | mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ 6 | mmail/../interfac/mysystem.h mmail/bluewave.h mmail/compress.h 7 | compress.$(O): mmail/compress.cc mmail/compress.h mmail/mmail.h \ 8 | mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ 9 | mmail/../interfac/mysystem.h 10 | filelist.$(O): mmail/filelist.cc mmail/mmail.h mmail/../config.h \ 11 | mmail/../mmail/misc.h mmail/../mmail/resource.h \ 12 | mmail/../interfac/mysystem.h 13 | letter.$(O): mmail/letter.cc mmail/mmail.h mmail/../config.h \ 14 | mmail/../mmail/misc.h mmail/../mmail/resource.h \ 15 | mmail/../interfac/mysystem.h 16 | misc.$(O): mmail/misc.cc mmail/mmail.h mmail/../config.h \ 17 | mmail/../mmail/misc.h mmail/../mmail/resource.h \ 18 | mmail/../interfac/mysystem.h 19 | mmail.$(O): mmail/mmail.cc mmail/mmail.h mmail/../config.h \ 20 | mmail/../mmail/misc.h mmail/../mmail/resource.h \ 21 | mmail/../interfac/mysystem.h mmail/compress.h \ 22 | mmail/../interfac/error.h mmail/bw.h mmail/pktbase.h mmail/bluewave.h \ 23 | mmail/qwk.h mmail/omen.h mmail/soup.h mmail/opx.h mmail/opxstrct.h 24 | netadd.$(O): mmail/netadd.cc mmail/mmail.h mmail/../config.h \ 25 | mmail/../mmail/misc.h mmail/../mmail/resource.h \ 26 | mmail/../interfac/mysystem.h 27 | omen.$(O): mmail/omen.cc mmail/omen.h mmail/pktbase.h mmail/mmail.h \ 28 | mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ 29 | mmail/../interfac/mysystem.h mmail/compress.h 30 | opx.$(O): mmail/opx.cc mmail/opx.h mmail/pktbase.h mmail/mmail.h \ 31 | mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ 32 | mmail/../interfac/mysystem.h mmail/opxstrct.h mmail/compress.h 33 | pktbase.$(O): mmail/pktbase.cc mmail/mmail.h mmail/../config.h \ 34 | mmail/../mmail/misc.h mmail/../mmail/resource.h \ 35 | mmail/../interfac/mysystem.h mmail/compress.h mmail/pktbase.h 36 | qwk.$(O): mmail/qwk.cc mmail/qwk.h mmail/pktbase.h mmail/mmail.h \ 37 | mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ 38 | mmail/../interfac/mysystem.h mmail/compress.h 39 | read.$(O): mmail/read.cc mmail/mmail.h mmail/../config.h \ 40 | mmail/../mmail/misc.h mmail/../mmail/resource.h \ 41 | mmail/../interfac/mysystem.h mmail/compress.h 42 | resource.$(O): mmail/resource.cc mmail/mmail.h mmail/../config.h \ 43 | mmail/../mmail/misc.h mmail/../mmail/resource.h \ 44 | mmail/../interfac/mysystem.h mmail/../interfac/error.h 45 | soup.$(O): mmail/soup.cc mmail/soup.h mmail/pktbase.h mmail/mmail.h \ 46 | mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ 47 | mmail/../interfac/mysystem.h mmail/compress.h 48 | addrbook.$(O): interfac/addrbook.cc interfac/interfac.h \ 49 | interfac/../mmail/mmail.h interfac/../mmail/../config.h \ 50 | interfac/../mmail/../mmail/misc.h \ 51 | interfac/../mmail/../mmail/resource.h \ 52 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 53 | interfac/isoconv.h 54 | ansiview.$(O): interfac/ansiview.cc interfac/interfac.h \ 55 | interfac/../mmail/mmail.h interfac/../mmail/../config.h \ 56 | interfac/../mmail/../mmail/misc.h \ 57 | interfac/../mmail/../mmail/resource.h \ 58 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 59 | interfac/isoconv.h 60 | arealist.$(O): interfac/arealist.cc interfac/interfac.h \ 61 | interfac/../mmail/mmail.h interfac/../mmail/../config.h \ 62 | interfac/../mmail/../mmail/misc.h \ 63 | interfac/../mmail/../mmail/resource.h \ 64 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 65 | interfac/isoconv.h 66 | basic.$(O): interfac/basic.cc interfac/interfac.h interfac/../mmail/mmail.h \ 67 | interfac/../mmail/../config.h interfac/../mmail/../mmail/misc.h \ 68 | interfac/../mmail/../mmail/resource.h \ 69 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 70 | interfac/isoconv.h 71 | help.$(O): interfac/help.cc interfac/interfac.h interfac/../mmail/mmail.h \ 72 | interfac/../mmail/../config.h interfac/../mmail/../mmail/misc.h \ 73 | interfac/../mmail/../mmail/resource.h \ 74 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 75 | interfac/isoconv.h 76 | interfac.$(O): interfac/interfac.cc interfac/error.h interfac/interfac.h \ 77 | interfac/../mmail/mmail.h interfac/../mmail/../config.h \ 78 | interfac/../mmail/../mmail/misc.h \ 79 | interfac/../mmail/../mmail/resource.h \ 80 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 81 | interfac/isoconv.h 82 | isoconv.$(O): interfac/isoconv.cc interfac/interfac.h \ 83 | interfac/../mmail/mmail.h interfac/../mmail/../config.h \ 84 | interfac/../mmail/../mmail/misc.h \ 85 | interfac/../mmail/../mmail/resource.h \ 86 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 87 | interfac/isoconv.h 88 | letterl.$(O): interfac/letterl.cc interfac/interfac.h \ 89 | interfac/../mmail/mmail.h interfac/../mmail/../config.h \ 90 | interfac/../mmail/../mmail/misc.h \ 91 | interfac/../mmail/../mmail/resource.h \ 92 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 93 | interfac/isoconv.h 94 | letterw.$(O): interfac/letterw.cc interfac/interfac.h \ 95 | interfac/../mmail/mmail.h interfac/../mmail/../config.h \ 96 | interfac/../mmail/../mmail/misc.h \ 97 | interfac/../mmail/../mmail/resource.h \ 98 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 99 | interfac/isoconv.h 100 | lettpost.$(O): interfac/lettpost.cc interfac/interfac.h \ 101 | interfac/../mmail/mmail.h interfac/../mmail/../config.h \ 102 | interfac/../mmail/../mmail/misc.h \ 103 | interfac/../mmail/../mmail/resource.h \ 104 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 105 | interfac/isoconv.h 106 | main.$(O): interfac/main.cc interfac/error.h interfac/interfac.h \ 107 | interfac/../mmail/mmail.h interfac/../mmail/../config.h \ 108 | interfac/../mmail/../mmail/misc.h \ 109 | interfac/../mmail/../mmail/resource.h \ 110 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 111 | interfac/isoconv.h 112 | mmcolor.$(O): interfac/mmcolor.cc interfac/interfac.h \ 113 | interfac/../mmail/mmail.h interfac/../mmail/../config.h \ 114 | interfac/../mmail/../mmail/misc.h \ 115 | interfac/../mmail/../mmail/resource.h \ 116 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 117 | interfac/isoconv.h 118 | mysystem.$(O): interfac/mysystem.cc interfac/interfac.h \ 119 | interfac/../mmail/mmail.h interfac/../mmail/../config.h \ 120 | interfac/../mmail/../mmail/misc.h \ 121 | interfac/../mmail/../mmail/resource.h \ 122 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 123 | interfac/isoconv.h interfac/error.h 124 | packet.$(O): interfac/packet.cc interfac/interfac.h \ 125 | interfac/../mmail/mmail.h interfac/../mmail/../config.h \ 126 | interfac/../mmail/../mmail/misc.h \ 127 | interfac/../mmail/../mmail/resource.h \ 128 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 129 | interfac/isoconv.h 130 | tagline.$(O): interfac/tagline.cc interfac/interfac.h \ 131 | interfac/../mmail/mmail.h interfac/../mmail/../config.h \ 132 | interfac/../mmail/../mmail/misc.h \ 133 | interfac/../mmail/../mmail/resource.h \ 134 | interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ 135 | interfac/isoconv.h interfac/tagline.h 136 | -------------------------------------------------------------------------------- /interfac/help.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * help windows 4 | 5 | Copyright 1996-1997 Kolossvary Tamas 6 | Copyright 1997-2021 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #include "interfac.h" 10 | 11 | HelpWindow::HelpWindow() 12 | { 13 | baseReset(); 14 | } 15 | 16 | void HelpWindow::newHelpMenu(const char **keys, const char **func, int it) 17 | { 18 | if (mm.res.getInt(ExpertMode)) 19 | menu = 0; 20 | else { 21 | int x, y, z, end; 22 | 23 | items = it; 24 | 25 | menu = new Win(3, COLS - 2, LINES - 4, C_HELP2); 26 | 27 | midpos = (COLS / 2) - 6; 28 | endpos = COLS - 21; 29 | 30 | if (items < 10) 31 | end = items; 32 | else { 33 | end = base + 8; 34 | if (end > items) 35 | end = items; 36 | } 37 | for (z = base; z < end; z++) { 38 | if (keys[z]) { 39 | x = (z - base) / 3; 40 | switch ((z - base) % 3) { 41 | case 0: 42 | y = 2; 43 | break; 44 | case 1: 45 | y = midpos; 46 | break; 47 | default: 48 | y = endpos; 49 | } 50 | menu->attrib(C_HELP1); 51 | menu->put(x, y, ": "); 52 | menu->put(x, y + 2, func[z]); 53 | 54 | y -= strlen(keys[z]); 55 | 56 | menu->attrib(C_HELP2); 57 | menu->put(x, y, keys[z]); 58 | } 59 | } 60 | if (items > 9) { 61 | menu->put(2, endpos - 1, "O"); 62 | menu->attrib(C_HELP1); 63 | menu->put(2, endpos, ": Other functions"); 64 | } 65 | menu->delay_update(); 66 | } 67 | } 68 | 69 | void HelpWindow::h_packetlist() 70 | { 71 | static const char *keys[] = { 72 | "Q", "Enter", "S, $", 73 | "K", "/, .", "G", 74 | "U", "R", 75 | 76 | "A", "^T", "^Z", 77 | "B", "Space, F", "^X", 78 | "T", "|, ^" 79 | }, *func[] = { 80 | "Quit", "select packet", "change Sort type", 81 | "Kill packet", "search / next", "Go to directory", 82 | "Update list", "Rename packet", 83 | 84 | "Addressbook", "Tagline editor", "command shell", 85 | "alias for PgUp", "aliases for PgDn", "eXit now", 86 | "Touch file", "filter list" 87 | }; 88 | 89 | newHelpMenu(keys, func, 16); 90 | } 91 | 92 | void HelpWindow::h_arealist() 93 | { 94 | static const char *keys[] = { 95 | "Q", "Enter", "F2, !", 96 | "E", "S, Ins", "U, Del", 97 | "L", "-", "+" 98 | }, *func[] = { 99 | "back to packet list", "select area", "Make reply packet", 100 | "Enter letter in area", "Subscribe", "Unsubscribe", 101 | "all/subscribed/active", "prev non-empty", "next non-empty" 102 | }; 103 | 104 | newHelpMenu(keys, func, 9); 105 | } 106 | 107 | void HelpWindow::h_letterlist() 108 | { 109 | static const char *keys[] = { 110 | "L", "Enter", "$", 111 | "E", "^F", "S", 112 | "U", "M", 113 | 114 | "A", "^T", "F2, !", 115 | "^E", "-", "+" 116 | }, *func[] = { 117 | "List all/unread/marked", "read letter", "change sort type", 118 | "Enter letter in area", "Forward letter", "Save (all/marked)", 119 | "Unread/read toggle", "Mark/unmark", 120 | 121 | "Addressbook", "Tagline editor", "make reply packet", 122 | "Enter from addressbook", "previous unread", "next unread" 123 | }, *repkeys[] = { 124 | "K", "Enter", "$", 125 | "E", "^F", "S", 126 | 0, "^B", 127 | 128 | "A", "^T", "F2, !" 129 | }, *repfunc[] = { 130 | "Kill letter", "read letter", "change sort type", 131 | "Edit letter", "Forward letter", "Save (all/marked)", 132 | 0, "Break into parts", 133 | 134 | "Addressbook", "Tagline editor", "Make reply packet" 135 | }; 136 | 137 | if (!mm.areaList->isReplyArea()) 138 | newHelpMenu(keys, func, 14); 139 | else 140 | newHelpMenu(repkeys, repfunc, 11); 141 | } 142 | 143 | void HelpWindow::h_letter(bool isAnsi) 144 | { 145 | enum {width = 60, citems = 18, regitems = 7, repitems = 3, ansitems = 13}; 146 | 147 | static const char *common[citems] = { 148 | "S - Save letter", 149 | "A - Addressbook", 150 | "C - toggle Character set", 151 | "D - Decrypt (rot13) toggle", 152 | "X - eXtra (hidden) lines", 153 | "I - Ignore soft CRs toggle", 154 | "^T - Tagline editor", 155 | "^F - Forward letter", 156 | "F2, ! - Make reply packet", 157 | "V, ^V, ^A - ANSI viewer", 158 | "/ - start a search", 159 | ". - repeat last search", 160 | "- - previous letter", 161 | "+, Enter - next letter", 162 | "Space - page through area", 163 | "^Z - command shell", 164 | "Q - back to letter list", 165 | "^X - eXit " MM_NAME " now" 166 | }, *regular[] = { 167 | "E - Enter new letter (post)", 168 | "R - Reply (followup)", 169 | "O - reply to Original sender", 170 | "N - Netmail/Email reply", 171 | "T - Take tagline", 172 | "M - Mark/unmark letter", 173 | "U - Unread/read toggle" 174 | }, *reply[] = { 175 | "K - Kill letter", 176 | "E, R - [Re-]Edit letter", 177 | "^B - Break reply into parts" 178 | }, *ansi[] = { 179 | "S - Save to file", 180 | "C - toggle Character set", 181 | "V, A, ^A - Animate", 182 | "/ - start a search", 183 | ". - repeat last search", 184 | "Space - page down/next", 185 | "- - previous", 186 | "+ - next", 187 | "@ - toggle at-code parsing", 188 | "| - toggle pipe codes", 189 | "1 - toggle Synchronet codes", 190 | "^V - toggle AVATAR parsing", 191 | "^B - toggle BSAVE parsing", 192 | "Q - Quit ANSI viewer" 193 | }; 194 | 195 | int extras = isAnsi ? ansitems : mm.areaList->isReplyArea() ? 196 | repitems : regitems; 197 | int usecommon = isAnsi ? 0 : citems; 198 | int height = ((extras + usecommon + 1) >> 1) + 4; 199 | 200 | menu = new ShadowedWin(height, width, (LINES - height) >> 1, C_HELP3); 201 | menu->attrib(C_HELP4); 202 | 203 | const char **extchar = isAnsi ? ansi : ((extras == 7) ? regular : reply); 204 | 205 | int x, line = 0; 206 | 207 | for (x = 0; x < extras; x++) { 208 | if (!(x & 1)) 209 | line++; 210 | menu->put(line, (x & 1) ? (width >> 1) + 2 : 2, extchar[x]); 211 | } 212 | 213 | for (x = extras; x < usecommon + extras; x++) { 214 | if (!(x & 1)) 215 | line++; 216 | menu->put(line, (x & 1) ? (width >> 1) + 2 : 2, common[x - extras]); 217 | } 218 | 219 | menu->put(line + 2, 2, "Plus the standard direction keys"); 220 | 221 | menu->wtouch(); 222 | } 223 | 224 | void HelpWindow::MakeActive() 225 | { 226 | switch (ui.active()) { 227 | case ansi_help: 228 | h_letter(true); 229 | break; 230 | case letter_help: 231 | h_letter(false); 232 | break; 233 | case letterlist: 234 | h_letterlist(); 235 | break; 236 | case arealist: 237 | h_arealist(); 238 | break; 239 | case packetlist: 240 | h_packetlist(); 241 | default:; 242 | } 243 | } 244 | 245 | void HelpWindow::Delete() 246 | { 247 | switch (ui.active()) { 248 | case ansi_help: 249 | case letter_help: 250 | delete (ShadowedWin *) menu; 251 | break; 252 | case letterlist: 253 | case arealist: 254 | case packetlist: 255 | delete menu; 256 | default:; 257 | } 258 | } 259 | 260 | void HelpWindow::baseNext() 261 | { 262 | if (items > 9) { 263 | base += 8; 264 | if (base > (items - 1)) 265 | base = 0; 266 | } 267 | Delete(); 268 | MakeActive(); 269 | } 270 | 271 | void HelpWindow::baseReset() 272 | { 273 | base = 0; 274 | } 275 | -------------------------------------------------------------------------------- /interfac/letterl.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * message list 4 | 5 | Copyright 1996 Kolossvary Tamas 6 | Copyright 1997 John Zero 7 | Copyright 1997-2021 William McBrine 8 | Distributed under the GNU General Public License, version 3 or later. */ 9 | 10 | #include "interfac.h" 11 | 12 | LetterListWindow::LetterListWindow() 13 | { 14 | lsorttype = mm.res.getInt(LetterSort); 15 | } 16 | 17 | void LetterListWindow::listSave() 18 | { 19 | static const char *saveopts[] = { "Marked", "All", "This one", "Quit" }; 20 | 21 | int marked = !(!mm.areaList->getNoOfMarked()); 22 | 23 | int status = ui.WarningWindow("Save which?", saveopts + !marked, 24 | 3 + marked); 25 | if (status) { 26 | bool saveok = ui.letterwindow.Save(status); 27 | if ((status == 1) && saveok) 28 | Move(KEY_DOWN); 29 | } 30 | } 31 | 32 | void LetterListWindow::Next() 33 | { 34 | do { 35 | Move(KEY_DOWN); 36 | mm.letterList->gotoActive(active); 37 | } while (mm.letterList->getRead() && ((active + 1) < NumOfItems())); 38 | } 39 | 40 | void LetterListWindow::FirstUnread() 41 | { 42 | position = 0; 43 | active = 0; 44 | } 45 | 46 | void LetterListWindow::Prev() 47 | { 48 | do { 49 | Move(KEY_UP); 50 | mm.letterList->gotoActive(active); 51 | } while (mm.letterList->getRead() && (active > 0)); 52 | } 53 | 54 | int LetterListWindow::NumOfItems() 55 | { 56 | return mm.letterList->noOfActive(); 57 | } 58 | 59 | void LetterListWindow::oneLine(int i) 60 | { 61 | mm.letterList->gotoActive(position + i); 62 | 63 | int st = mm.letterList->getStatus(); 64 | 65 | char *p = list->lineBuf; 66 | p += sprintf(p, format, (st & MS_MARKED) ? 'M' : 67 | (st & MS_SAVED) ? 's' : ' ', (st & MS_REPLIED) ? '~' : ' ', 68 | (st & MS_READ) ? '*' : ' ', mm.letterList->getMsgNum(), 69 | mm.letterList->getFrom(), mm.letterList->getTo(), 70 | stripre(mm.letterList->getSubject())); 71 | 72 | if (mm.areaList->isCollection()) { 73 | const char *origArea = mm.letterList->getNewsgrps(); 74 | if (!origArea) 75 | origArea = mm.areaList->getDescription(mm.letterList->getAreaID()); 76 | if (origArea) 77 | sprintf(p - 15, " %-13.13s ", origArea); 78 | } 79 | 80 | coltype linecol = mm.letterList->isPersonal() ? C_LLPERSONAL : C_LISTWIN; 81 | 82 | letterconv_in(list->lineBuf); 83 | DrawOne(i, (st & MS_READ) ? noemph(linecol) : emph(linecol)); 84 | } 85 | 86 | searchret LetterListWindow::oneSearch(int x, const char *item, int mode) 87 | { 88 | searchret retval; 89 | 90 | mm.letterList->gotoActive(x); 91 | retval = mm.letterList->filterCheck(item) ? True : False; 92 | 93 | if (!retval && (mode == s_fulltext)) { 94 | ui.changestate(letter); 95 | ui.letterwindow.setPos(-1); 96 | retval = ui.letterwindow.search(item); 97 | if (retval != True) 98 | ui.changestate(letterlist); 99 | } 100 | 101 | return retval; 102 | } 103 | 104 | void LetterListWindow::setFormat() 105 | { 106 | char topformat[50]; 107 | unsigned char tot, maxFromLen, maxToLen, maxSubjLen; 108 | 109 | tot = COLS - 19; 110 | maxSubjLen = tot / 2; 111 | tot -= maxSubjLen; 112 | maxToLen = tot / 2; 113 | maxFromLen = tot - maxToLen; 114 | 115 | if (!mm.areaList->hasTo() || (mm.areaList->isCollection() && 116 | !mm.areaList->isReplyArea())) { 117 | 118 | maxSubjLen += maxToLen; 119 | maxToLen = 0; 120 | } 121 | 122 | if (mm.areaList->isReplyArea()) { 123 | maxSubjLen += maxFromLen; 124 | maxFromLen = 0; 125 | } 126 | 127 | sprintf(format, "%%c%%c%%c%%6ld %%-%d.%ds %%-%d.%ds %%-%d.%ds", 128 | maxFromLen, maxFromLen, maxToLen, maxToLen, maxSubjLen, 129 | maxSubjLen); 130 | 131 | sprintf(topformat, " Msg#%s", format + 10); 132 | sprintf(topline, topformat, "From", "To", "Subject"); 133 | } 134 | 135 | void LetterListWindow::MakeActiveCore() 136 | { 137 | static const char *llmodes[] = {"All", "Unread", "Marked"}, 138 | *llsorts[] = {"subject", "number", "from", "to"}; 139 | 140 | int maxbott = LINES - (mm.res.getInt(ExpertMode) ? 7 : 11); 141 | list_max_y = (NumOfItems() < maxbott) ? NumOfItems() : maxbott; 142 | 143 | bool too_many = (NumOfItems() > list_max_y); 144 | 145 | const char *modestr = llmodes[mm.letterList->getMode()]; 146 | const char *sortstr = llsorts[lsorttype]; 147 | const char *pn = mm.res.get(PacketName); 148 | const char *filter = mm.letterList->getFilter(); 149 | 150 | int pnlen = strlen(pn); 151 | if (pnlen > 20) 152 | pnlen = 20; 153 | int offset = strlen(modestr) + pnlen + 3; 154 | int flen = filter ? strlen(filter) + 3 : 0; 155 | if (flen > 20) 156 | flen = 20; 157 | int nwidth = COLS - (too_many ? 27 : 19) - offset - flen - strlen(sortstr); 158 | 159 | char *title = new char[COLS + 1]; 160 | 161 | char *end = title + sprintf(title, "%.*s | %s in %.*s", pnlen, pn, 162 | modestr, nwidth, mm.areaList->getDescription()); 163 | 164 | char *newend = end + sprintf(end, ", by %s", sortstr); 165 | if (too_many) 166 | newend += sprintf(newend, " (%d)", NumOfItems()); 167 | if (flen) 168 | sprintf(newend, " | %.*s", flen - 3, filter); 169 | 170 | areaconv_in(title); 171 | 172 | borderCol = C_LLBBORD; 173 | 174 | list = new InfoWin(list_max_y + 3, list_max_x + 2, 2, borderCol, 175 | title, C_LLTOPTEXT1); 176 | 177 | list->attrib(C_LLTOPTEXT2); 178 | 179 | *end = '\0'; 180 | offset += 3; 181 | list->put(0, offset + 3, title + offset); 182 | 183 | delete[] title; 184 | 185 | list->attrib(C_LLHEAD); 186 | list->put(1, 3, topline); 187 | 188 | if (mm.areaList->isCollection()) 189 | list->put(1, COLS - 19, "Area"); 190 | list->touch(); 191 | 192 | DrawAll(); 193 | Select(); 194 | } 195 | 196 | void LetterListWindow::MakeActive() 197 | { 198 | top_offset = 2; 199 | list_max_x = COLS - 6; 200 | 201 | topline = new char[COLS + 1]; 202 | 203 | setFormat(); 204 | ui.areas.Select(); 205 | MakeActiveCore(); 206 | } 207 | 208 | void LetterListWindow::Select() 209 | { 210 | mm.letterList->gotoActive(active); 211 | } 212 | 213 | void LetterListWindow::ResetActive() 214 | { 215 | active = mm.letterList->getActive(); 216 | } 217 | 218 | void LetterListWindow::Delete() 219 | { 220 | delete list; 221 | delete[] topline; 222 | } 223 | 224 | bool LetterListWindow::extrakeys(int key) 225 | { 226 | Select(); 227 | switch (key) { 228 | #ifdef USE_MOUSE 229 | case MM_MOUSE: 230 | { 231 | int begx = list->xstart(), begy = list->ystart(); 232 | 233 | if (mm_mouse_event.y == begy) { 234 | if ((mm_mouse_event.x > (begx + 12)) && 235 | (mm_mouse_event.x < (begx + 27))) 236 | 237 | extrakeys('L'); 238 | else 239 | if ((mm_mouse_event.x > (begx + 27)) && 240 | (mm_mouse_event.x < (begx + list_max_x))) 241 | 242 | extrakeys('$'); 243 | } 244 | } 245 | break; 246 | #endif 247 | case 'U': 248 | case 'M': // Toggle read/unread and marked from letterlist 249 | mm.letterList->setStatus(mm.letterList->getStatus() ^ 250 | ((key == 'U') ? MS_READ : MS_MARKED)); 251 | ui.setAnyRead(); 252 | Move(KEY_DOWN); 253 | Draw(); 254 | break; 255 | case 5: 256 | case 'E': 257 | if (mm.areaList->isReplyArea()) 258 | ui.letterwindow.KeyHandle('E'); 259 | else 260 | if (!(mm.areaList->getType() & (COLLECTION | READONLY))) { 261 | if ((5 == key) || mm.areaList->isEmail()) 262 | ui.addressbook(); 263 | ui.letterwindow.EnterLetter(mm.areaList->getAreaNo(), 'E'); 264 | } else 265 | ui.nonFatalError("Cannot reply there"); 266 | break; 267 | case 2: 268 | case 6: 269 | case MM_DEL: 270 | case 'K': 271 | if (mm.areaList->isReplyArea()) 272 | ui.letterwindow.KeyHandle(key); 273 | break; 274 | case 'L': 275 | mm.letterList->relist(); 276 | ResetActive(); 277 | ui.redraw(); 278 | break; 279 | case '$': 280 | mm.letterList->resort(); 281 | ui.redraw(); 282 | break; 283 | case 'S': 284 | listSave(); 285 | ui.redraw(); 286 | } 287 | return false; 288 | } 289 | 290 | void LetterListWindow::setFilter(const char *item) 291 | { 292 | mm.letterList->setFilter(item); 293 | } 294 | -------------------------------------------------------------------------------- /mmail/filelist.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * file_header and file_list 4 | 5 | Copyright 1996-1997 Toth Istvan 6 | Copyright 1998-2021 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #include "mmail.h" 10 | 11 | extern "C" int fnamecomp(const void *a, const void *b) 12 | { 13 | int d; 14 | 15 | const char *p = (*((file_header **) a))->getName(); 16 | const char *q = (*((file_header **) b))->getName(); 17 | 18 | d = strcasecmp(p, q); 19 | if (!d) 20 | d = strcmp(q, p); 21 | 22 | return d; 23 | } 24 | 25 | extern "C" int ftimecomp(const void *a, const void *b) 26 | { 27 | long result = (*((file_header **) b))->getDate() - 28 | (*((file_header **) a))->getDate(); 29 | return (result > 0) ? 1 : (result < 0) ? -1 : 0; 30 | } 31 | 32 | // ---------------------------------------------------------------- 33 | // file_header methods 34 | // ---------------------------------------------------------------- 35 | file_header::file_header(const char *nameA, time_t dateA, off_t sizeA) : 36 | date(dateA), size(sizeA) 37 | { 38 | name = strdupplus(nameA); 39 | next = 0; 40 | } 41 | 42 | file_header::~file_header() 43 | { 44 | delete[] name; 45 | } 46 | 47 | const char *file_header::getName() const 48 | { 49 | return name; 50 | } 51 | 52 | time_t file_header::getDate() const 53 | { 54 | return date; 55 | } 56 | 57 | void file_header::setDate() 58 | { 59 | date = touchFile(name); 60 | } 61 | 62 | off_t file_header::getSize() const 63 | { 64 | return size; 65 | } 66 | 67 | // -------------------------------------------------------------- 68 | // file_list methods 69 | // -------------------------------------------------------------- 70 | 71 | file_list::file_list(const char *FileDir, bool sorttypeA, bool dirlistA) : 72 | sorttype(sorttypeA), dirlist(dirlistA) 73 | { 74 | DirName = strdupplus(FileDir); 75 | filter = 0; 76 | relist(); 77 | } 78 | 79 | file_list::~file_list() 80 | { 81 | cleanup(); 82 | 83 | delete[] DirName; 84 | delete[] filter; 85 | } 86 | 87 | void file_list::cleanup() 88 | { 89 | while (noOfFiles) 90 | delete files[--noOfFiles]; 91 | delete[] files; 92 | 93 | while (noOfDirs) 94 | delete dirs[--noOfDirs]; 95 | delete[] dirs; 96 | } 97 | 98 | void file_list::relist() 99 | { 100 | if (!myopendir(DirName)) 101 | fatalError("There is no Packet Dir!"); 102 | 103 | noOfFiles = noOfDirs = 0; 104 | 105 | file_header head("", 0, 0), dirhead("", 0, 0); 106 | file_header *filept = &head, *dirpt = &dirhead; 107 | 108 | const char *fname; 109 | mystat st; 110 | 111 | while ((fname = myreaddir(st)) != 0) 112 | if (!filter || searchstr(fname, filter)) 113 | if (dirlist || !st.isdir()) 114 | if (strcmp(fname, ".")) 115 | if (st.readable()) { 116 | file_header *trec = new file_header(fname, st.fdate(), 117 | st.fsize()); 118 | if (st.isdir()) { 119 | dirpt->next = trec; 120 | dirpt = dirpt->next; 121 | noOfDirs++; 122 | } else { 123 | filept->next = trec; 124 | filept = filept->next; 125 | noOfFiles++; 126 | } 127 | } 128 | 129 | int c; 130 | 131 | if (noOfDirs > 0) { 132 | dirs = new file_header *[noOfDirs]; 133 | dirpt = dirhead.next; 134 | 135 | c = 0; 136 | while (dirpt) { 137 | dirs[c++] = dirpt; 138 | dirpt = dirpt->next; 139 | } 140 | 141 | if (noOfDirs > 1) 142 | qsort(dirs, noOfDirs, sizeof(file_header *), fnamecomp); 143 | } else 144 | dirs = 0; 145 | 146 | files = new file_header *[noOfFiles]; 147 | filept = head.next; 148 | 149 | c = 0; 150 | while (filept) { 151 | files[c++] = filept; 152 | filept = filept->next; 153 | } 154 | sort(); 155 | } 156 | 157 | void file_list::sort() 158 | { 159 | if (noOfFiles > 1) 160 | qsort(files, noOfFiles, sizeof(file_header *), 161 | sorttype ? ftimecomp : fnamecomp); 162 | } 163 | 164 | void file_list::resort() 165 | { 166 | sorttype = !sorttype; 167 | sort(); 168 | } 169 | 170 | const char *file_list::getDirName() const 171 | { 172 | return DirName; 173 | } 174 | 175 | int file_list::getNoOfDirs() const 176 | { 177 | return noOfDirs; 178 | } 179 | 180 | int file_list::getNoOfFiles() const 181 | { 182 | return noOfFiles; 183 | } 184 | 185 | void file_list::gotoFile(int fileNo) 186 | { 187 | if (fileNo < (noOfFiles + noOfDirs)) 188 | activeFile = fileNo; 189 | } 190 | 191 | char *file_list::changeDir(const char *newpath) 192 | { 193 | char *newdir = 0; 194 | 195 | if (dirlist) { 196 | if (!newpath) 197 | newpath = getName(); 198 | 199 | mychdir(DirName); 200 | if (!mychdir(newpath)) 201 | newdir = mygetcwd(); 202 | } 203 | return newdir; 204 | } 205 | 206 | int file_list::changeName(const char *newname) 207 | { 208 | mychdir(DirName); 209 | return rename(getName(), newname); 210 | } 211 | 212 | file_header *file_list::base() const 213 | { 214 | return (activeFile < noOfDirs) ? dirs[activeFile] : 215 | files[activeFile - noOfDirs]; 216 | } 217 | 218 | file_header *file_list::base(int i) const 219 | { 220 | return (i < noOfDirs) ? dirs[i] : files[i - noOfDirs]; 221 | } 222 | 223 | const char *file_list::getName() const 224 | { 225 | return base()->getName(); 226 | } 227 | 228 | time_t file_list::getDate() const 229 | { 230 | return base()->getDate(); 231 | } 232 | 233 | void file_list::setDate() 234 | { 235 | base()->setDate(); 236 | } 237 | 238 | off_t file_list::getSize() const 239 | { 240 | return base()->getSize(); 241 | } 242 | 243 | const char *file_list::getNext(const char *fname) 244 | { 245 | if (fname) { 246 | bool isExt = (*fname == '.'); 247 | 248 | for (int c = activeFile + 1; c < (noOfFiles + noOfDirs); c++) { 249 | const char *q = base(c)->getName(); 250 | 251 | if (isExt) { 252 | size_t len = strlen(q); 253 | if (len > 5) { 254 | const char *p = q + len - 4; 255 | if (!strcasecmp(p, fname)) { 256 | activeFile = c; 257 | return q; 258 | } 259 | } 260 | } else 261 | if (!strncasecmp(q, fname, strlen(fname))) { 262 | activeFile = c; 263 | return q; 264 | } 265 | } 266 | } 267 | return 0; 268 | } 269 | 270 | file_header *file_list::getNextF(const char *fname) 271 | { 272 | return getNext(fname) ? base() : 0; 273 | } 274 | 275 | const char *file_list::exists(const char *fname) 276 | { 277 | gotoFile(-1); 278 | return getNext(fname); 279 | } 280 | 281 | file_header *file_list::existsF(const char *fname) 282 | { 283 | return exists(fname) ? base() : 0; 284 | } 285 | 286 | void file_list::addItem(file_header **list, const char *q, int &filecount) 287 | { 288 | file_header *p; 289 | int x; 290 | 291 | gotoFile(-1); 292 | while ((p = getNextF(q)) != 0) { 293 | for (x = 0; x < filecount; x++) 294 | if (list[x] == p) 295 | break; 296 | if (x == filecount) { 297 | list[x] = p; 298 | filecount++; 299 | } 300 | } 301 | } 302 | 303 | char *file_list::expandName(const char *fname) 304 | { 305 | return fullpath(DirName, fname); 306 | } 307 | 308 | const char *file_list::find_name(const char *fname) 309 | { 310 | const char *p = exists(fname); 311 | return p ? p : fname; 312 | } 313 | 314 | FILE *file_list::ftryopen(const char *fname) 315 | { 316 | FILE *f; 317 | 318 | const char *p = exists(fname); 319 | if (p) { 320 | char *q = expandName(p); 321 | f = fopen(q, "rb"); 322 | delete[] q; 323 | } else 324 | f = 0; 325 | 326 | return f; 327 | } 328 | 329 | void file_list::kill() 330 | { 331 | if (activeFile >= noOfDirs) { 332 | int i = activeFile - noOfDirs; 333 | 334 | char *fname = expandName(getName()); 335 | remove(fname); 336 | delete[] fname; 337 | 338 | delete files[i]; 339 | noOfFiles--; 340 | for (; i < noOfFiles; i++) 341 | files[i] = files[i + 1]; 342 | } 343 | } 344 | 345 | int file_list::nextNumExt(const char *baseName) 346 | { 347 | int retval = -1; 348 | const char *nextName; 349 | 350 | gotoFile(-1); 351 | do { 352 | nextName = getNext(baseName); 353 | if (nextName) { 354 | int newval = getNumExt(nextName); 355 | 356 | if (newval > retval) 357 | retval = newval; 358 | } 359 | } while (nextName); 360 | 361 | if (retval == 999) 362 | retval = -1; 363 | 364 | return ++retval; 365 | } 366 | 367 | const char *file_list::getFilter() const 368 | { 369 | return filter; 370 | } 371 | 372 | void file_list::setFilter(const char *newfilter) 373 | { 374 | delete[] filter; 375 | filter = (newfilter && *newfilter) ? strdupplus(newfilter) : 0; 376 | 377 | cleanup(); 378 | relist(); 379 | 380 | if (filter && !(noOfDirs + noOfFiles)) { 381 | delete[] filter; 382 | filter = 0; 383 | 384 | cleanup(); 385 | relist(); 386 | } 387 | } 388 | -------------------------------------------------------------------------------- /interfac/tagline.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * tagline selection, editing 4 | 5 | Copyright 1996-1997 Kolossvary Tamas 6 | Copyright 1997-2021 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #include "interfac.h" 10 | 11 | extern "C" int tnamecmp(const void *a, const void *b) 12 | { 13 | int d; 14 | 15 | const char *p = (*((tagline **) a))->text; 16 | const char *q = (*((tagline **) b))->text; 17 | 18 | d = strcasecmp(p, q); 19 | if (!d) 20 | d = strcmp(q, p); 21 | 22 | return d; 23 | } 24 | 25 | tagline::tagline(const char *tag) 26 | { 27 | if (tag) 28 | strnzcpy(text, tag, TAGLINE_LENGTH); 29 | killed = false; 30 | next = 0; 31 | } 32 | 33 | TaglineWindow::TaglineWindow() 34 | { 35 | nodraw = true; 36 | sorted = false; 37 | NumOfTaglines = NumOfActive = 0; 38 | taglist = tagactive = 0; 39 | filter = 0; 40 | } 41 | 42 | TaglineWindow::~TaglineWindow() 43 | { 44 | DestroyChain(); 45 | } 46 | 47 | void TaglineWindow::MakeActive() 48 | { 49 | int expmode = mm.res.getInt(ExpertMode); 50 | nodraw = false; 51 | 52 | list_max_y = LINES - (expmode ? 12 : 15); 53 | 54 | int xwidth = COLS - 4; 55 | if (xwidth > (TAGLINE_LENGTH + 2)) 56 | xwidth = TAGLINE_LENGTH + 2; 57 | list_max_x = xwidth - 2; 58 | 59 | top_offset = 1; 60 | 61 | borderCol = C_TBBACK; 62 | 63 | char tmp[60]; 64 | char *p = tmp + sprintf(tmp, "Taglines, %s", sorted ? "sorted" : 65 | "unsorted"); 66 | if (NumOfActive > list_max_y) 67 | p += sprintf(p, " (%d)", NumOfActive); 68 | if (filter) 69 | sprintf(p, " | %.20s", filter); 70 | 71 | list = new InfoWin(LINES - 10, xwidth, 5, borderCol, tmp, C_TTEXT, 72 | expmode ? 2 : 5, top_offset); 73 | 74 | if (!expmode) { 75 | int x = xwidth / 3 + 1; 76 | int y = list_max_y + 1; 77 | 78 | list->attrib(C_TKEYSTEXT); 79 | list->horizline(y); 80 | list->put(++y, 2, "Q"); 81 | list->put(y, x, "R"); 82 | list->put(y, x * 2, "Enter"); 83 | list->put(++y, 2, "K"); 84 | list->put(y, x, "A"); 85 | //list->put(y, x * 2, " /, ."); 86 | list->put(y, x * 2 + 4, "E"); 87 | list->attrib(C_TTEXT); 88 | list->put(y, 3, ": Kill current tagline"); 89 | list->put(y, x + 1, ": Add new tagline"); 90 | //list->put(y, x * 2 + 5, ": search / next"); 91 | list->put(y, x * 2 + 5, ": Edit tagline"); 92 | list->put(--y, 3, ": don't apply tagline"); 93 | list->put(y, x + 1, ": Random select tagline"); 94 | list->put(y, x * 2 + 5, ": apply tagline"); 95 | } 96 | DrawAll(); 97 | } 98 | 99 | void TaglineWindow::Delete() 100 | { 101 | delete list; 102 | nodraw = true; 103 | } 104 | 105 | bool TaglineWindow::extrakeys(int key) 106 | { 107 | switch (key) { 108 | case 'A': 109 | EnterTagline(); 110 | break; 111 | case 'E': 112 | EditTagline(); 113 | break; 114 | case 'R': 115 | RandomTagline(); 116 | break; 117 | case MM_DEL: 118 | case 'K': 119 | if (highlighted) 120 | kill(); 121 | break; 122 | case 'S': 123 | case '$': 124 | sorted = !sorted; 125 | MakeChain(); 126 | Delete(); 127 | MakeActive(); 128 | } 129 | return false; 130 | } 131 | 132 | void TaglineWindow::setFilter(const char *item) 133 | { 134 | delete[] filter; 135 | filter = strdupplus(item); 136 | MakeChain(); 137 | if (!NumOfActive) { 138 | delete[] filter; 139 | filter = 0; 140 | MakeChain(); 141 | } 142 | } 143 | 144 | void TaglineWindow::RandomTagline() 145 | { 146 | int i = rand() / (RAND_MAX / NumOfActive); 147 | 148 | Move(KEY_HOME); 149 | for (int j = 1; j <= i; j++) 150 | Move(KEY_DOWN); 151 | DrawAll(); 152 | } 153 | 154 | void TaglineWindow::EnterTagline(const char *tag) 155 | { 156 | FILE *fd; 157 | char newtagline[TAGLINE_LENGTH + 1]; 158 | int y; 159 | 160 | Move(KEY_END); 161 | if (NumOfActive >= list_max_y) { 162 | y = list_max_y; 163 | position++; 164 | } else 165 | y = NumOfActive + 1; 166 | active++; 167 | 168 | if (!nodraw) { 169 | NumOfActive++; 170 | Draw(); 171 | NumOfActive--; 172 | } else { 173 | int xwidth = COLS - 4; 174 | if (xwidth > (TAGLINE_LENGTH + 2)) 175 | xwidth = TAGLINE_LENGTH + 2; 176 | list = new InfoWin(5, xwidth, (LINES - 5) >> 1, C_TBBACK); 177 | list->attrib(C_TTEXT); 178 | list->put(1, 1, "Enter new tagline:"); 179 | list->update(); 180 | } 181 | 182 | strcpy(newtagline, tag ? tag : ""); 183 | 184 | if (list->getstring(nodraw ? 2 : y, 1, newtagline, TAGLINE_LENGTH, 185 | C_TENTER, C_TENTERGET)) { 186 | 187 | cropesp(newtagline); 188 | 189 | if (newtagline[0]) { 190 | 191 | //check dupes; also move curr to end of list: 192 | bool found = false; 193 | 194 | curr = &head; 195 | while (curr->next && !found) { 196 | curr = curr->next; 197 | found = !strcmp(newtagline, curr->text); 198 | } 199 | 200 | if (!found) { 201 | curr->next = new tagline(newtagline); 202 | fd = fopen(tagname, "at"); 203 | if (fd) { 204 | fputs(newtagline, fd); 205 | fputc('\n', fd); 206 | fclose(fd); 207 | } 208 | NumOfTaglines++; 209 | 210 | MakeChain(); 211 | } else 212 | ui.nonFatalError("Already in file"); 213 | } 214 | } 215 | Move(KEY_END); 216 | 217 | if (!nodraw) { 218 | DrawAll(); 219 | doupdate(); 220 | } else 221 | list->update(); 222 | } 223 | 224 | void TaglineWindow::EditTagline() 225 | { 226 | char newtagline[TAGLINE_LENGTH + 1]; 227 | 228 | strcpy(newtagline, getCurrent()); 229 | if (list->getstring(active - position + 1, 1, newtagline, 230 | TAGLINE_LENGTH, C_TENTER, C_TENTERGET)) { 231 | 232 | cropesp(newtagline); 233 | if (newtagline[0]) 234 | strcpy(tagactive[active]->text, newtagline); 235 | } 236 | WriteFile(false); 237 | Draw(); 238 | } 239 | 240 | void TaglineWindow::kill() 241 | { 242 | if (ui.WarningWindow("Remove this tagline?")) { 243 | if (position) 244 | position--; 245 | 246 | highlighted->killed = true; 247 | NumOfTaglines--; 248 | 249 | MakeChain(); 250 | 251 | WriteFile(false); 252 | } 253 | Delete(); 254 | MakeActive(); 255 | } 256 | 257 | bool TaglineWindow::ReadFile() 258 | { 259 | FILE *fd; 260 | char newtag[TAGLINE_LENGTH + 1]; 261 | bool flag; 262 | 263 | fd = fopen(tagname, "rt"); 264 | flag = !(!fd); 265 | 266 | if (flag) { 267 | char *end; 268 | 269 | curr = &head; 270 | do { 271 | end = myfgets(newtag, sizeof newtag, fd); 272 | 273 | if (end && (newtag[0] != '\n')) { 274 | if (*end == '\n') 275 | *end = '\0'; 276 | curr->next = new tagline(newtag); 277 | curr = curr->next; 278 | NumOfTaglines++; 279 | } 280 | } while (end); 281 | fclose(fd); 282 | } 283 | return flag; 284 | } 285 | 286 | void TaglineWindow::WriteFile(bool message) 287 | { 288 | FILE *tagx; 289 | 290 | if (message) 291 | printf("Creating %s...\n", tagname); 292 | 293 | tagx = fopen(tagname, "wt"); 294 | if (tagx) { 295 | for (int x = 0; x < NumOfTaglines; x++) { 296 | fputs(taglist[x]->text, tagx); 297 | fputc('\n', tagx); 298 | } 299 | fclose(tagx); 300 | } 301 | } 302 | 303 | void TaglineWindow::MakeChain() 304 | { 305 | delete[] taglist; 306 | taglist = new tagline *[NumOfTaglines + 1]; 307 | 308 | delete[] tagactive; 309 | tagactive = new tagline *[NumOfTaglines + 1]; 310 | 311 | NumOfActive = 0; 312 | 313 | if (NumOfTaglines) { 314 | curr = head.next; 315 | int c = 0; 316 | while (curr) { 317 | if (!curr->killed) { 318 | taglist[c++] = curr; 319 | if (!filter || searchstr(curr->text, filter)) 320 | tagactive[NumOfActive++] = curr; 321 | } 322 | curr = curr->next; 323 | } 324 | 325 | if (sorted && (NumOfActive > 1)) 326 | qsort(tagactive, NumOfActive, sizeof(tagline *), tnamecmp); 327 | } 328 | 329 | tagactive[NumOfTaglines] = 0; // hack for EnterTagline 330 | } 331 | 332 | void TaglineWindow::DestroyChain() 333 | { 334 | while (NumOfTaglines) 335 | delete taglist[--NumOfTaglines]; 336 | delete[] taglist; 337 | delete[] tagactive; 338 | } 339 | 340 | void TaglineWindow::oneLine(int i) 341 | { 342 | int z = position + i; 343 | curr = (z < NumOfActive) ? tagactive[z] : 0; 344 | 345 | if (z == active) 346 | highlighted = curr; 347 | 348 | sprintf(list->lineBuf, "%-*.*s", list_max_x, list_max_x, 349 | curr ? curr->text : " "); 350 | 351 | DrawOne(i, C_TLINES); 352 | } 353 | 354 | searchret TaglineWindow::oneSearch(int x, const char *item, int) 355 | { 356 | return searchstr(tagactive[x]->text, item) ? True : False; 357 | } 358 | 359 | int TaglineWindow::NumOfItems() 360 | { 361 | return NumOfActive; 362 | } 363 | 364 | // Create tagline file if it doesn't exist. 365 | void TaglineWindow::Init() 366 | { 367 | // Default taglines: 368 | #include "tagline.h" 369 | 370 | tagname = mm.res.get(TaglineFile); 371 | 372 | bool useDefault = !ReadFile(); 373 | 374 | if (useDefault) { 375 | curr = &head; 376 | for (const char **p = defaultTags; *p; p++) { 377 | curr->next = new tagline(*p); 378 | curr = curr->next; 379 | NumOfTaglines++; 380 | } 381 | } 382 | 383 | MakeChain(); 384 | 385 | if (useDefault) 386 | WriteFile(true); 387 | } 388 | 389 | const char *TaglineWindow::getCurrent() 390 | { 391 | return tagactive[active]->text; 392 | } 393 | -------------------------------------------------------------------------------- /interfac/mmcolor.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * color handling, and default colors 4 | 5 | Copyright 1998-2021 William McBrine , 6 | Ingo Brueckl 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #include "interfac.h" 10 | 11 | chtype emph(coltype chA) 12 | { 13 | chtype ch = ColorArray[chA]; 14 | 15 | if (has_colors()) 16 | switch (PAIR_NUMBER(ch) & 7) { 17 | case COLOR_BLACK: 18 | case COLOR_RED: 19 | case COLOR_BLUE: 20 | ch |= A_BOLD; 21 | } 22 | else 23 | ch |= A_BOLD; 24 | return ch; 25 | } 26 | 27 | chtype noemph(coltype chA) 28 | { 29 | chtype ch = ColorArray[chA]; 30 | 31 | if (has_colors()) 32 | switch (PAIR_NUMBER(ch) & 7) { 33 | case COLOR_WHITE: 34 | case COLOR_YELLOW: 35 | case COLOR_GREEN: 36 | case COLOR_CYAN: 37 | case COLOR_MAGENTA: 38 | ch |= A_BOLD; 39 | } 40 | return ch; 41 | } 42 | 43 | chtype ColorClass::allcolors[numColors] = { 44 | COL(COLOR_WHITE, COLOR_BLACK), //Start screen/backgnd 45 | COL(COLOR_BLUE, COLOR_BLACK) | A_BOLD, //Start/bdr 46 | COL(COLOR_MAGENTA, COLOR_BLACK), //Start screen/bottom 47 | 48 | COL(COLOR_WHITE, COLOR_BLACK) | A_BOLD, //Help desc. 49 | COL(COLOR_YELLOW, COLOR_BLACK) | A_BOLD, //Help keys 50 | COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //Help 2 bdr 51 | COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Help 2 text 52 | 53 | COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Welcome bdr 54 | COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Welcome prog name 55 | COL(COLOR_CYAN, COLOR_BLUE) | A_BOLD, //Welcome auth names 56 | 57 | COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Add. backgnd 58 | COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //Add. headers 59 | COL(COLOR_CYAN, COLOR_BLUE) | A_BOLD, //Address book/text 60 | 61 | COL(COLOR_WHITE, COLOR_RED) | A_BOLD, //Warn/text 62 | COL(COLOR_YELLOW, COLOR_RED) | A_BOLD, //Warn/hilight 63 | 64 | COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //Letter/text 65 | COL(COLOR_CYAN, COLOR_BLUE), //Letter/quoted text 66 | COL(COLOR_CYAN, COLOR_BLUE), //Letter/tagline 67 | COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //Letter/tear 68 | COL(COLOR_GREEN, COLOR_BLUE), //Letter/hidden 69 | COL(COLOR_CYAN, COLOR_BLUE), //Letter/origin 70 | COL(COLOR_MAGENTA, COLOR_WHITE) | A_REVERSE, //Letter/bottom statline 71 | 72 | COL(COLOR_BLUE, COLOR_CYAN), //Letter/header text 73 | COL(COLOR_BLACK, COLOR_CYAN), //msgnum 74 | COL(COLOR_BLACK, COLOR_CYAN), //from 75 | COL(COLOR_BLACK, COLOR_CYAN), //to 76 | COL(COLOR_BLACK, COLOR_CYAN), //subject 77 | COL(COLOR_BLACK, COLOR_CYAN), //date 78 | COL(COLOR_CYAN, COLOR_BLACK) | A_REVERSE, //flags high 79 | COL(COLOR_WHITE, COLOR_CYAN), //flags 80 | 81 | COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Packet/header 82 | COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //line text 83 | COL(COLOR_CYAN, COLOR_BLUE) | A_BOLD, //Packet/lines 84 | 85 | COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //Little area 86 | COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //line text 87 | 88 | COL(COLOR_GREEN, COLOR_BLUE), //Area list/reply area 89 | COL(COLOR_CYAN, COLOR_BLUE), //Area list/normal 90 | COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //info win 91 | COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //filled text 92 | COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //border text 93 | COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //border 94 | COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //header text 95 | 96 | COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //Letter text 97 | COL(COLOR_CYAN, COLOR_BLUE), //Letter/enter get1 98 | COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //get2 99 | COL(COLOR_WHITE, COLOR_RED) | A_BOLD, //Letter/save border 100 | COL(COLOR_WHITE, COLOR_RED) | A_BOLD, //Letter/save 101 | COL(COLOR_YELLOW, COLOR_RED) | A_BOLD, //get 102 | 103 | COL(COLOR_WHITE, COLOR_BLUE), //Letter list/top text1 104 | COL(COLOR_GREEN, COLOR_BLUE), //Letter list/personal 105 | COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Letter list 106 | 107 | COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //top text1 108 | COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //areaname 109 | COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //headers 110 | 111 | COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Tagline 112 | COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //Tagline/text 113 | COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //key select 114 | COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //Tagline/enter 115 | COL(COLOR_CYAN, COLOR_BLUE) | A_BOLD, //enter get 116 | COL(COLOR_CYAN, COLOR_BLUE) | A_BOLD, //lines 117 | 118 | COL(COLOR_WHITE, COLOR_WHITE) | A_BOLD //All black! 119 | }; 120 | 121 | const char *ColorClass::col_names[numColors] = { 122 | 123 | "Main_Back", "Main_Border", "Main_BottSeparator", 124 | "BottHelp_Descrip", "BottHelp_Keys", "Help_Border", "Help_Text", 125 | "Welcome_Border", "Welcome_Header", "Welcome_Text", 126 | "Address_Border", "Address_Descrip", "Address_List", "Warn_Text", 127 | "Warn_Keys", "Letter_Text", "Letter_Quoted", "Letter_Tagline", 128 | "Letter_Tearline", "Letter_Hidden", "Letter_Origin", 129 | "Letter_Border", "LH_Text", "LH_Msgnum", "LH_From", "LH_To", 130 | "LH_Subject", "LH_Date", "LH_FlagsHigh", "LH_Flags", 131 | "Packet_Border", "Packet_Header", "Packet_List", 132 | "LittleArea_Header", "LittleArea_List", "Area_Reply", "Area_List", 133 | "Area_InfoDescrip", "Area_InfoText", "Area_TopText", "Area_Border", 134 | "Area_Header", "HeadEdit_Text", "HeadEdit_Input1", 135 | "HeadEdit_Input2", "Save_Border", "Save_Header", "Save_Input", 136 | "LettList_Text", "LettList_Personal", "LettList_Border", 137 | "LettList_TopText", "LettList_Area", "LettList_Header", 138 | "Tag_Border", "Tag_Text", "Tag_Keys", "Tag_Input1", "Tag_Input2", 139 | "Tag_List", "Shadow" 140 | }; 141 | 142 | const char *ColorClass::col_intro[] = { 143 | "---------------", 144 | "Color selection", 145 | "---------------", 146 | "", 147 | "The format is \"ItemName: , , \"", 148 | "Colors are Black, Blue, Green, Cyan, Red, Magenta, Yellow, and White.", 149 | "Attributes are Bold or Reverse.", 150 | "", 151 | "If no color for ItemName is defined, the default will be used. (Defaults", 152 | "are shown below.) Lines beginning with '#' are commented out.", 153 | 0 154 | }; 155 | 156 | const char *ColorClass::col_comments[numColors] = { 157 | "Background colors", 0, 0, 158 | "Bottom help window", 0, 159 | "Pop-up help", 0, 160 | "Welcome window (vanity plate)", 0, 0, 161 | "Address book", 0, 0, 162 | "Warning window", 0, 163 | "Letter window", 0, 0, 0, 0, 0, 0, 164 | "Letter header", 0, 0, 0, 0, 0, 0, 0, 165 | "Packet list", 0, 0, 166 | "Little area list", 0, 167 | "Area list", 0, 0, 0, 0, 0, 0, 168 | "Header editor", 0, 0, 169 | "Save filename input", 0, 0, 170 | "Letter list", 0, 0, 0, 0, 0, 171 | "Taglines", 0, 0, 0, 0, 0, 172 | "Shadows" 173 | }; 174 | 175 | const chtype ColorClass::mapped[] = {COLOR_BLACK, COLOR_BLUE, COLOR_GREEN, 176 | COLOR_CYAN, COLOR_RED, COLOR_MAGENTA, COLOR_YELLOW, COLOR_WHITE}; 177 | 178 | // analyze a ,, color string 179 | chtype ColorClass::colorparse(const char *colorstring) 180 | { 181 | static const char *const cnames[] = {"bla", "blu", "gre", "cya", 182 | "red", "mag", "yel", "whi", "bol", "rev"}; 183 | 184 | static chtype c[] = {COLOR_WHITE, COLOR_BLACK}; 185 | chtype att = A_NORMAL; 186 | 187 | char *pos = (char *) colorstring; 188 | 189 | for (int i = 0; (i < 3) && *pos; i++) { 190 | 191 | while (*pos == ' ' || *pos == '\t') 192 | pos++; 193 | 194 | for (int j = 0; j < 10; j++) 195 | if (!strncasecmp(pos, cnames[j], 3)) { 196 | switch (j) { 197 | case 8: 198 | att = A_BOLD; 199 | break; 200 | case 9: 201 | att = A_REVERSE; 202 | break; 203 | default: 204 | c[i] = mapped[j]; 205 | } 206 | break; 207 | } 208 | 209 | while (*pos && *pos != ',') 210 | pos++; 211 | if (*pos == ',') 212 | pos++; 213 | } 214 | 215 | // Swap white-on-white for black-on-black: 216 | if ((c[0] == COLOR_BLACK) && (c[1] == COLOR_BLACK)) 217 | return COL(COLOR_WHITE, COLOR_WHITE) | att; 218 | else 219 | return COL(c[0], c[1]) | att; 220 | } 221 | 222 | void ColorClass::processOne(int c, const char *resValue) 223 | { 224 | allcolors[c] = colorparse(resValue); 225 | } 226 | 227 | const char *ColorClass::configLineOut(int x) 228 | { 229 | return decompose(allcolors[x]); 230 | } 231 | 232 | const char *ColorClass::findcol(chtype ch) 233 | { 234 | static const char *const cnames[] = {"Black", "Blue", "Green", 235 | "Cyan", "Red", "Magenta", "Yellow", "White", ""}; 236 | int x; 237 | for (x = 0; x < 8; x++) 238 | if (ch == mapped[x]) 239 | break; 240 | return cnames[x]; 241 | } 242 | 243 | const char *ColorClass::decompose(chtype ch) 244 | { 245 | static char compost[26]; 246 | chtype fg, bg, bold, rev; 247 | 248 | fg = PAIR_NUMBER(ch) >> 3; 249 | bg = PAIR_NUMBER(ch) & 7; 250 | bold = ch & A_BOLD; 251 | rev = ch & A_REVERSE; 252 | 253 | // Swap black-on-black for white-on-white: 254 | if ((fg == (COLOR_WHITE)) && (bg == (COLOR_WHITE))) 255 | fg = bg = COLOR_BLACK; 256 | 257 | char *p = compost; 258 | p += sprintf(p, "%s, %s", findcol(fg), findcol(bg)); 259 | 260 | if (bold) 261 | sprintf(p, ", Bold"); 262 | else if (rev) 263 | sprintf(p, ", Reverse"); 264 | 265 | return compost; 266 | } 267 | 268 | void ColorClass::Init() 269 | { 270 | const char *configname = mm.res.get(ColorFile); 271 | bool usecol = mm.res.getInt(UseColors); 272 | 273 | names = col_names; 274 | intro = col_intro; 275 | comments = col_comments; 276 | configItemNum = numColors; 277 | 278 | if (usecol) 279 | if (parseConfig(configname)) 280 | newConfig(configname); 281 | 282 | ColorArray = allcolors; 283 | } 284 | -------------------------------------------------------------------------------- /interfac/addrbook.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * address book 4 | 5 | Copyright 1996 Kolossvary Tamas 6 | Copyright 1997-2021 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #include "interfac.h" 10 | 11 | extern "C" int perscomp(const void *a, const void *b) 12 | { 13 | return strcasecmp((*((Person **) a))->name, 14 | (*((Person **) b))->name); 15 | } 16 | 17 | Person::Person(const char *sname, const char *saddr) 18 | { 19 | if (saddr && (*saddr == 'I')) 20 | saddr++; 21 | netmail_addr = saddr; 22 | setname(sname); 23 | killed = false; 24 | next = 0; 25 | } 26 | 27 | Person::Person(const char *sname, net_address &naddr) 28 | { 29 | netmail_addr = naddr; 30 | setname(sname); 31 | killed = false; 32 | next = 0; 33 | } 34 | 35 | Person::~Person() 36 | { 37 | delete[] name; 38 | } 39 | 40 | void Person::setname(const char *sname) 41 | { 42 | name = strdupplus(sname); 43 | } 44 | 45 | void Person::dump(FILE *fd) 46 | { 47 | fprintf(fd, (netmail_addr.isInternet ? "%s\nI%s\n\n" : "%s\n%s\n\n"), 48 | name, (const char *) netmail_addr); 49 | } 50 | 51 | AddressBook::AddressBook() 52 | { 53 | NumOfPersons = NumOfActive = 0; 54 | people = living = 0; 55 | filter = 0; 56 | } 57 | 58 | AddressBook::~AddressBook() 59 | { 60 | DestroyChain(); 61 | } 62 | 63 | void AddressBook::MakeActive(bool NoEnterA) 64 | { 65 | int expmode = mm.res.getInt(ExpertMode); 66 | statetype s = ui.prevactive(); 67 | if (s != address) 68 | inletter = ((s == letter) || (s == littlearealist)) && 69 | !mm.areaList->isReplyArea(); 70 | 71 | NoEnter = NoEnterA; 72 | 73 | list_max_y = LINES - (expmode ? 9 : 12); 74 | list_max_x = COLS - 6; 75 | top_offset = 2; 76 | 77 | borderCol = C_ADDR1; 78 | 79 | char tmp[60]; 80 | char *p = tmp + sprintf(tmp, "Addresses"); 81 | if (NumOfActive > list_max_y) 82 | p += sprintf(p, " (%d)", NumOfActive); 83 | if (filter) 84 | sprintf(p, " | %.20s", filter); 85 | 86 | int xwidth = list_max_x + 2; 87 | list = new InfoWin(LINES - 6, xwidth, 2, borderCol, tmp, C_ADDR2, 88 | expmode ? 3 : 6); 89 | 90 | list->attrib(C_ADDR2); 91 | list->put(1, 2, "Name Netmail address"); 92 | 93 | if (!expmode) { 94 | int x = xwidth / 3 + 1; 95 | int y = list_max_y + 2; 96 | 97 | list->horizline(y); 98 | 99 | list->put(++y, 3, ": Quit addressbook"); 100 | list->put(y, x + 3, ": Add new address"); 101 | list->put(y, x * 2 + 1, ": search / next"); 102 | list->put(++y, 3, ": Kill current address"); 103 | list->put(y, x + 3, ": Edit address"); 104 | 105 | if (inletter) 106 | list->put(y, x * 2 + 1, ": address from Letter"); 107 | 108 | list->attrib(C_ADDR1); 109 | list->put(y, 2, "K"); 110 | list->put(y, x + 2, "E"); 111 | 112 | if (inletter) 113 | list->put(y, x * 2, "L"); 114 | 115 | list->put(--y, 2, "Q"); 116 | list->put(y, x + 2, "A"); 117 | list->put(y, x * 2 - 3, "/, ."); 118 | } 119 | DrawAll(); 120 | } 121 | 122 | void AddressBook::Delete() 123 | { 124 | delete list; 125 | } 126 | 127 | bool AddressBook::extrakeys(int key) 128 | { 129 | switch (key) { 130 | case MM_ENTER: 131 | SetLetterThings(); 132 | break; 133 | case 'A': 134 | NewAddress(); 135 | break; 136 | case 'E': 137 | ChangeAddress(); 138 | break; 139 | case MM_DEL: 140 | case 'K': 141 | kill(); 142 | break; 143 | case 'L': 144 | GetAddress(); 145 | } 146 | return false; 147 | } 148 | 149 | void AddressBook::setFilter(const char *item) 150 | { 151 | delete[] filter; 152 | filter = strdupplus(item); 153 | MakeChain(); 154 | if (!NumOfActive) { 155 | delete[] filter; 156 | filter = 0; 157 | MakeChain(); 158 | } 159 | } 160 | 161 | void AddressBook::WriteFile() 162 | { 163 | FILE *fd = fopen(addfname, "wt"); 164 | for (int x = 0; x < NumOfPersons; x++) 165 | people[x]->dump(fd); 166 | fclose(fd); 167 | } 168 | 169 | void AddressBook::kill() 170 | { 171 | if (highlighted) { 172 | if (ui.WarningWindow("Remove this address?")) { 173 | highlighted->killed = true; 174 | 175 | if (position) 176 | position--; 177 | 178 | NumOfPersons--; 179 | 180 | MakeChain(); 181 | WriteFile(); 182 | } 183 | Delete(); 184 | MakeActive(NoEnter); 185 | } 186 | } 187 | 188 | void AddressBook::SetLetterThings() 189 | { 190 | if (!NoEnter && highlighted) 191 | ui.letterwindow.set_Letter_Params(highlighted->netmail_addr, 192 | highlighted->name); 193 | } 194 | 195 | void AddressBook::Add(const char *Name, net_address &Address) 196 | { 197 | if (Address.isSet) { 198 | bool found = false; 199 | 200 | // Dupe check; also positions curr at end of list: 201 | curr = &head; 202 | while (curr->next && !found) { 203 | curr = curr->next; 204 | found = (curr->netmail_addr == Address) && 205 | !strcasecmp(curr->name, Name); 206 | } 207 | 208 | if (!found) { 209 | curr->next = new Person(Name, Address); 210 | 211 | FILE *fd = fopen(addfname, "at"); 212 | curr->next->dump(fd); 213 | fclose(fd); 214 | 215 | NumOfPersons++; 216 | MakeChain(); 217 | 218 | active = NumOfPersons; 219 | Draw(); 220 | } else 221 | ui.nonFatalError("Already in addressbook"); 222 | } else 223 | ui.nonFatalError("No address found"); 224 | } 225 | 226 | void AddressBook::GetAddress() 227 | { 228 | if (inletter) 229 | Add(mm.letterList->getFrom(), ui.letterwindow.PickNetAddr()); 230 | } 231 | 232 | int AddressBook::HeaderLine(ShadowedWin &win, char *buf, int limit, 233 | int pos, coltype color) 234 | { 235 | int getit = win.getstring(pos, 8, buf, limit, color, color); 236 | return getit; 237 | } 238 | 239 | int AddressBook::Edit(Person &p) 240 | { 241 | char NAME[100], NETADD[100]; 242 | 243 | const int maxitems = 2; 244 | int result, current = 0; 245 | bool end = false; 246 | 247 | if (p.netmail_addr.isSet) { 248 | strnzcpy(NAME, p.name, 99); 249 | strnzcpy(NETADD, (const char *) p.netmail_addr, 99); 250 | } else 251 | NAME[0] = NETADD[0] = '\0'; 252 | 253 | ShadowedWin add_edit(4, COLS - 4, (LINES >> 1) - 2, C_LETEXT); 254 | 255 | add_edit.put(1, 2, "Name:"); 256 | add_edit.put(2, 2, "Addr:"); 257 | 258 | add_edit.attrib(C_LEGET1); 259 | add_edit.put(1, 8, NAME); 260 | 261 | add_edit.attrib(C_LEGET2); 262 | add_edit.put(2, 8, NETADD); 263 | 264 | add_edit.update(); 265 | 266 | do { 267 | result = HeaderLine(add_edit, current ? NETADD : NAME, 99, 268 | current + 1, current ? C_LEGET2 : C_LEGET1); 269 | 270 | switch (result) { 271 | case 0: 272 | end = true; 273 | break; 274 | case 1: 275 | current++; 276 | if (current == maxitems) 277 | end = true; 278 | break; 279 | case 2: 280 | if (current > 0) 281 | current--; 282 | break; 283 | case 3: 284 | if (current < maxitems - 1) 285 | current++; 286 | } 287 | } while (!end); 288 | 289 | if (result && NAME[0] && NETADD[0]) { 290 | p.setname(NAME); 291 | p.netmail_addr = NETADD; 292 | if (!p.netmail_addr.isSet) 293 | result = 0; 294 | } else 295 | result = 0; 296 | return result; 297 | } 298 | 299 | void AddressBook::NewAddress() 300 | { 301 | Person p; 302 | 303 | p.netmail_addr.isSet = false; 304 | if (Edit(p)) 305 | Add(p.name, p.netmail_addr); 306 | ui.redraw(); 307 | } 308 | 309 | void AddressBook::ChangeAddress() 310 | { 311 | if (highlighted) 312 | if (Edit(*highlighted)) 313 | WriteFile(); 314 | ui.redraw(); 315 | } 316 | 317 | void AddressBook::oneLine(int i) 318 | { 319 | int z = position + i; 320 | curr = (z < NumOfActive) ? living[z] : 0; 321 | 322 | if (z == active) 323 | highlighted = curr; 324 | 325 | char *tmp = list->lineBuf; 326 | int x = curr ? sprintf(tmp, " %-31s %s", curr->name, 327 | (const char *) curr->netmail_addr) : 0; 328 | for (; x < list_max_x; x++) 329 | tmp[x] = ' '; 330 | tmp[x] = '\0'; 331 | 332 | DrawOne(i, C_ADDR3); 333 | } 334 | 335 | searchret AddressBook::oneSearch(int x, const char *item, int) 336 | { 337 | const char *s; 338 | 339 | s = searchstr(living[x]->name, item); 340 | if (!s) 341 | s = searchstr(living[x]->netmail_addr, item); 342 | return s ? True : False; 343 | } 344 | 345 | int AddressBook::NumOfItems() 346 | { 347 | return NumOfActive; 348 | } 349 | 350 | void AddressBook::ReadFile() 351 | { 352 | FILE *fd; 353 | char name[256], nmaddr[256], other[256]; 354 | bool end = false; 355 | 356 | fd = fopen(addfname, "rt"); 357 | if (fd) { 358 | curr = &head; 359 | while (!end) { 360 | do 361 | end = !myfgets(name, sizeof name, fd); 362 | while (name[0] == '\n' && !end); 363 | 364 | end = !myfgets(nmaddr, sizeof nmaddr, fd); 365 | 366 | if (!end) { 367 | strtok(name, "\n"); 368 | strtok(nmaddr, "\n"); 369 | curr->next = new Person(name, nmaddr); 370 | curr = curr->next; 371 | NumOfPersons++; 372 | } 373 | 374 | do 375 | end = !myfgets(other, sizeof other, fd); 376 | while (other[0] != '\n' && !end); 377 | } 378 | fclose(fd); 379 | } 380 | 381 | MakeChain(); 382 | 383 | if (NumOfPersons > 1) { 384 | qsort(people, NumOfPersons, sizeof(Person *), perscomp); 385 | if (NumOfActive > 1) 386 | qsort(living, NumOfActive, sizeof(Person *), perscomp); 387 | ReChain(); 388 | } 389 | } 390 | 391 | 392 | void AddressBook::MakeChain() 393 | { 394 | delete[] people; 395 | delete[] living; 396 | 397 | NumOfActive = 0; 398 | 399 | if (NumOfPersons) { 400 | people = new Person *[NumOfPersons]; 401 | living = new Person *[NumOfPersons]; 402 | 403 | curr = head.next; 404 | int c = 0; 405 | while (curr) { 406 | if (!curr->killed) { 407 | people[c++] = curr; 408 | if (!filter || (searchstr(curr->name, filter) 409 | || searchstr(curr->netmail_addr, filter))) 410 | living[NumOfActive++] = curr; 411 | } 412 | curr = curr->next; 413 | } 414 | } else 415 | people = living = 0; 416 | } 417 | 418 | void AddressBook::ReChain() 419 | { 420 | head.next = people[0]; 421 | for (int c = 0; c < (NumOfPersons - 1); c++) 422 | people[c]->next = people[c + 1]; 423 | 424 | people[NumOfPersons - 1]->next = 0; 425 | } 426 | 427 | void AddressBook::DestroyChain() 428 | { 429 | while (NumOfPersons) 430 | delete people[--NumOfPersons]; 431 | delete[] people; 432 | } 433 | 434 | void AddressBook::Init() 435 | { 436 | addfname = mm.res.get(AddressFile); 437 | ReadFile(); 438 | } 439 | -------------------------------------------------------------------------------- /interfac/packet.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * packet list window, vanity plate 4 | 5 | Copyright 1996 Kolossvary Tamas 6 | Copyright 1997 John Zero 7 | Copyright 1997-2021 William McBrine 8 | Distributed under the GNU General Public License, version 3 or later. */ 9 | 10 | #include "interfac.h" 11 | 12 | #ifdef VANITY_PLATE 13 | 14 | void Welcome::MakeActive() 15 | { 16 | window = new ShadowedWin(6, 50, 2, C_WBORDER); 17 | window->attrib(C_WELCOME1); 18 | window->put(1, 7, "Welcome to " MM_NAME " Offline Reader!"); 19 | window->attrib(C_WELCOME2); 20 | window->put(3, 2, "Copyright (c) " MM_YEAR); 21 | window->put(3, 21, "William McBrine, Kolossvary"); 22 | window->put(4, 7, "Tamas, Toth Istvan, John Zero, et al."); 23 | window->touch(); 24 | } 25 | 26 | void Welcome::Delete() 27 | { 28 | delete window; 29 | } 30 | 31 | #endif 32 | 33 | PacketListWindow::oneDir::oneDir(const char *nameA, oneDir *parentA) : 34 | parent(parentA) 35 | { 36 | name = fixPath(nameA); 37 | position = active = 0; 38 | } 39 | 40 | PacketListWindow::oneDir::~oneDir() 41 | { 42 | delete[] name; 43 | } 44 | 45 | PacketListWindow::PacketListWindow() 46 | { 47 | #ifdef HAS_HOME 48 | home = getenv("HOME"); 49 | #endif 50 | origDir = 0; 51 | packetList = 0; 52 | } 53 | 54 | void PacketListWindow::init() 55 | { 56 | mychdir(mm.res.get(PacketDir)); 57 | char *tmp = mygetcwd(); 58 | origDir = new oneDir(tmp, 0); 59 | delete[] tmp; 60 | 61 | currDir = origDir; 62 | 63 | sorttype = mm.res.getInt(PacketSort); 64 | newList(); 65 | if (noFiles) // If there are any files, 66 | active = noDirs; // set active to the first one. 67 | } 68 | 69 | PacketListWindow::~PacketListWindow() 70 | { 71 | delete origDir; 72 | delete packetList; 73 | } 74 | 75 | void PacketListWindow::newList() 76 | { 77 | delete packetList; 78 | 79 | mm.res.set(oldPacketName, (char *) 0); 80 | 81 | const char *target = currDir->name; 82 | mm.res.set(PacketDir, target); 83 | 84 | time(&currTime); 85 | 86 | packetList = new file_list(target, sorttype, true); 87 | 88 | noDirs = packetList->getNoOfDirs(); 89 | noFiles = packetList->getNoOfFiles(); 90 | } 91 | 92 | void PacketListWindow::MakeActiveCore() 93 | { 94 | const int stline = 95 | #ifdef VANITY_PLATE 96 | 9; 97 | #else 98 | 2; 99 | #endif 100 | list_max_y = LINES - (stline + (mm.res.getInt(ExpertMode) ? 5 : 9)); 101 | 102 | bool usenum = false; 103 | int items = NumOfItems(); 104 | 105 | if (list_max_y > items) 106 | list_max_y = items ? items : 1; 107 | else 108 | usenum = true; 109 | 110 | char tmp[46], *dest = tmp, *src = currDir->name; 111 | int end, maxlen = 36, len = strlen(src); 112 | 113 | if (usenum) 114 | maxlen -= sprintf(tmp, " (%d)", noFiles); 115 | 116 | #ifdef HAS_HOME 117 | int hlen = home ? strlen(home) : 0; 118 | bool inhome = hlen && (hlen <= len) && !strncmp(home, currDir->name, hlen); 119 | 120 | if (inhome && ((len - hlen) < maxlen)) { 121 | tmp[0] = '~'; 122 | dest++; 123 | src += hlen; 124 | end = len - hlen + 1; 125 | } else 126 | #endif 127 | if (len > maxlen) { 128 | strcpy(tmp, "..."); 129 | dest += 3; 130 | src += (len - maxlen) + 3; 131 | end = maxlen; 132 | } else 133 | end = len; 134 | 135 | strcpy(dest, src); 136 | canonize(dest); 137 | 138 | int newend = end + sprintf(tmp + end, ", by %s", sorttype ? 139 | "time" : "name"); 140 | if (usenum) 141 | newend += sprintf(tmp + newend, " (%d)", noFiles); 142 | 143 | const char *filter = packetList->getFilter(); 144 | if (filter) { 145 | int flen = strlen(filter); 146 | int fmax = list_max_x - 5 - newend; 147 | if (flen > fmax) 148 | flen = fmax; 149 | 150 | sprintf(tmp + newend, " | %.*s", flen, filter); 151 | } 152 | 153 | list = new InfoWin(list_max_y + 3, list_max_x + 2, stline, borderCol, 154 | tmp, C_PHEADTEXT); 155 | 156 | list->attrib(C_PLINES); 157 | tmp[end] = '\0'; 158 | list->put(0, 3, tmp); 159 | 160 | list->attrib(C_PHEADTEXT); 161 | list->put(1, 3, "Packet Size Date"); 162 | list->touch(); 163 | 164 | DrawAll(); 165 | } 166 | 167 | void PacketListWindow::MakeActive() 168 | { 169 | list_max_x = 48; 170 | top_offset = 2; 171 | 172 | borderCol = C_PBBACK; 173 | 174 | #ifdef VANITY_PLATE 175 | welcome.MakeActive(); 176 | #endif 177 | MakeActiveCore(); 178 | } 179 | 180 | int PacketListWindow::NumOfItems() 181 | { 182 | return noDirs + noFiles; 183 | } 184 | 185 | void PacketListWindow::Delete() 186 | { 187 | delete list; 188 | #ifdef VANITY_PLATE 189 | welcome.Delete(); 190 | #endif 191 | } 192 | 193 | void PacketListWindow::oneLine(int i) 194 | { 195 | char *tmp = list->lineBuf; 196 | int absPos = position + i; 197 | time_t tmpt; 198 | 199 | packetList->gotoFile(absPos); 200 | 201 | if (absPos < noDirs) { 202 | absPos = sprintf(tmp, " <%.28s", packetList->getName()); 203 | char *tmp2 = tmp + absPos; 204 | *tmp2++ = '>'; 205 | 206 | absPos = 32 - absPos; 207 | while (--absPos > 0) 208 | *tmp2++ = ' '; 209 | } else { 210 | const char *tmp2 = packetList->getName(); 211 | 212 | strcpy(tmp, " "); 213 | 214 | if (*tmp2 == '.') 215 | sprintf(&tmp[2], "%-20.20s", tmp2); 216 | else { 217 | for (int j = 2; *tmp2 && (*tmp2 != '.') && (j < 10); j++) 218 | tmp[j] = *tmp2++; 219 | 220 | sprintf(&tmp[10], "%-10.10s", tmp2); 221 | } 222 | 223 | sprintf(&tmp[20], "%12lu", (unsigned long) packetList->getSize()); 224 | } 225 | 226 | tmpt = packetList->getDate(); 227 | 228 | #ifdef TIMEKLUDGE 229 | if (!tmpt) 230 | tmpt = currTime; 231 | #endif 232 | long dtime = currTime - tmpt; 233 | 234 | // 15000000 secs = approx six months (use year if older): 235 | strftime(&tmp[32], 17, ((dtime < 0 || dtime > 15000000L) ? 236 | " %b %d %Y " : " %b %d %H:%M "), localtime(&tmpt)); 237 | 238 | DrawOne(i, C_PLINES); 239 | } 240 | 241 | searchret PacketListWindow::oneSearch(int x, const char *item, int mode) 242 | { 243 | const char *s; 244 | searchret retval; 245 | 246 | packetList->gotoFile(x); 247 | 248 | s = packetList->getName(); 249 | retval = searchstr(s, item) ? True : False; 250 | 251 | if ((retval == False) && (x >= noDirs) && (mode < s_pktlist)) { 252 | int oldactive = active; 253 | active = x; 254 | if (OpenPacket() == PKT_OK) { 255 | mm.checkForReplies(); 256 | mm.openReply(); 257 | 258 | ui.redraw(); 259 | ui.ReportWindow("Searching (ESC to abort)..."); 260 | 261 | mm.areaList = new area_list(); 262 | mm.areaList->getRepList(); 263 | mm.initRead(); 264 | mm.areaList->setMode(-1); 265 | mm.areaList->relist(); 266 | ui.changestate(arealist); 267 | ui.areas.setActive(-1); 268 | retval = ui.areas.search(item, mode); 269 | if (retval != True) { 270 | active = oldactive; 271 | mm.Delete(); 272 | ui.changestate(packetlist); 273 | } 274 | } else 275 | active = oldactive; 276 | } 277 | 278 | return retval; 279 | } 280 | 281 | void PacketListWindow::Select() 282 | { 283 | packetList->gotoFile(active); 284 | } 285 | 286 | bool PacketListWindow::back() 287 | { 288 | bool end = false; 289 | 290 | if (currDir != origDir) { 291 | oneDir *oldDir = currDir->parent; 292 | delete currDir; 293 | currDir = oldDir; 294 | 295 | newList(); 296 | position = currDir->position; 297 | active = currDir->active; 298 | 299 | ui.redraw(); 300 | } else 301 | end = true; 302 | return end; 303 | } 304 | 305 | bool PacketListWindow::extrakeys(int key) 306 | { 307 | bool end = false; 308 | 309 | switch (key) { 310 | #ifdef USE_MOUSE 311 | case MM_MOUSE: 312 | { 313 | int begx = list->xstart(), begy = list->ystart(); 314 | 315 | if ( (mm_mouse_event.y != begy) || 316 | ((mm_mouse_event.x < (begx + 3)) || 317 | (mm_mouse_event.x > (begx + list_max_x))) ) 318 | 319 | break; 320 | } 321 | #endif 322 | case 'S': 323 | case '$': 324 | packetList->resort(); 325 | sorttype = !sorttype; 326 | delete list; 327 | MakeActiveCore(); 328 | break; 329 | case 'G': 330 | gotoDir(); 331 | break; 332 | case 'R': 333 | renamePacket(); 334 | break; 335 | case MM_DEL: 336 | case 'K': 337 | killPacket(); 338 | break; 339 | case 'T': 340 | Select(); 341 | packetList->setDate(); 342 | time(&currTime); 343 | delete list; 344 | MakeActiveCore(); 345 | break; 346 | case 'U': 347 | newList(); 348 | ui.redraw(); 349 | } 350 | return end; 351 | } 352 | 353 | void PacketListWindow::setFilter(const char *item) 354 | { 355 | packetList->setFilter(item); 356 | 357 | noDirs = packetList->getNoOfDirs(); 358 | noFiles = packetList->getNoOfFiles(); 359 | } 360 | 361 | bool PacketListWindow::newDir(const char *dname) 362 | { 363 | char *result = packetList->changeDir(homify(dname)); 364 | 365 | if (result) { 366 | currDir->position = position; 367 | currDir->active = active; 368 | 369 | oneDir *nd = new oneDir(result, currDir); 370 | currDir = nd; 371 | 372 | newList(); 373 | position = 0; 374 | active = noFiles ? noDirs : 0; 375 | 376 | delete[] result; 377 | return true; 378 | } 379 | return false; 380 | } 381 | 382 | void PacketListWindow::gotoDir() 383 | { 384 | char pathname[70]; 385 | pathname[0] = '\0'; 386 | 387 | if (ui.savePrompt("New directory:", pathname) && pathname[0]) { 388 | 389 | if (newDir(pathname)) 390 | ui.redraw(); 391 | else 392 | ui.nonFatalError("Could not change to directory"); 393 | 394 | } else 395 | ui.nonFatalError("Change cancelled"); 396 | } 397 | 398 | void PacketListWindow::renamePacket() 399 | { 400 | if (active >= noDirs) { 401 | Select(); 402 | 403 | const char *fname = packetList->getName(); 404 | 405 | char question[60], answer[60]; 406 | sprintf(question, "New filename for %.39s:", fname); 407 | 408 | if (getNumExt(fname) != -1) 409 | strnzcpy(answer, fname, 59); 410 | else { 411 | const char *base = findBaseName(fname); 412 | int ext = packetList->nextNumExt(base); 413 | 414 | sprintf(answer, "%.55s.%03d", base, ext); 415 | } 416 | 417 | if (ui.savePrompt(question, answer) && answer[0] && 418 | strcmp(fname, answer)) { 419 | 420 | const char *expanswer = homify(answer); 421 | 422 | bool changeit = !(packetList->exists(expanswer)); 423 | 424 | if (changeit) { 425 | Select(); 426 | changeit = !(packetList->changeName(expanswer)); 427 | 428 | if (changeit) { 429 | newList(); 430 | ui.redraw(); 431 | } else 432 | ui.nonFatalError("Rename failed"); 433 | } else 434 | ui.nonFatalError("Name already used"); 435 | } else 436 | ui.nonFatalError("Rename cancelled"); 437 | } 438 | } 439 | 440 | void PacketListWindow::killPacket() 441 | { 442 | if (active >= noDirs) { 443 | Select(); 444 | 445 | char tmp[128]; 446 | sprintf(tmp, "Do you really want to delete %.90s?", 447 | packetList->getName()); 448 | 449 | if (ui.WarningWindow(tmp)) { 450 | packetList->kill(); 451 | noFiles = packetList->getNoOfFiles(); 452 | } 453 | ui.redraw(); 454 | } 455 | } 456 | 457 | pktstatus PacketListWindow::OpenPacket() 458 | { 459 | Select(); 460 | if (active < noDirs) { 461 | 462 | if (newDir(0)) 463 | ui.redraw(); 464 | else 465 | ui.nonFatalError("Could not change to directory"); 466 | 467 | return NEW_DIR; 468 | } else 469 | return (active < NumOfItems()) ? 470 | mm.selectPacket(packetList->getName()) : PKT_UNFOUND; 471 | } 472 | -------------------------------------------------------------------------------- /interfac/arealist.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * area list 4 | 5 | Copyright 1996-1997 Kolossvary Tamas 6 | Copyright 1997-2021 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #include "interfac.h" 10 | 11 | // ***** LittleAreaListWindow ***** 12 | 13 | void LittleAreaListWindow::MakeActive() 14 | { 15 | position = 0; 16 | areanum = -1; 17 | 18 | mm.areaList->setMode(mm.areaList->getMode() - 1); 19 | mm.areaList->relist(); 20 | 21 | list_max_y = (NumOfItems() < LINES - 10) ? NumOfItems() : LINES - 10; 22 | list_max_x = 52; 23 | top_offset = 3; 24 | 25 | borderCol = C_LALBTEXT; 26 | 27 | list = new InfoWin(list_max_y + 4, list_max_x + 2, 3, borderCol, 0, 28 | C_SBACK, 4, top_offset); 29 | 30 | list->put(1, 2, "Reply goes to area:"); 31 | 32 | DrawAll(); 33 | } 34 | 35 | void LittleAreaListWindow::init() 36 | { 37 | int oldarea = mm.areaList->getAreaNo(); 38 | 39 | disp = 0; 40 | do 41 | mm.areaList->gotoActive(disp++); 42 | while (mm.areaList->isCollection()); 43 | disp--; 44 | 45 | mm.areaList->gotoArea(mm.letterList->getAreaID()); 46 | active = mm.areaList->getActive() - disp; 47 | 48 | // restore old area (for collection areas): 49 | mm.areaList->gotoArea(oldarea); 50 | } 51 | 52 | void LittleAreaListWindow::Select() 53 | { 54 | mm.areaList->gotoActive(active + disp); 55 | } 56 | 57 | void LittleAreaListWindow::Delete() 58 | { 59 | delete list; 60 | } 61 | 62 | int LittleAreaListWindow::getArea() 63 | { 64 | return areanum; 65 | } 66 | 67 | int LittleAreaListWindow::NumOfItems() 68 | { 69 | return mm.areaList->noOfActive() - disp; 70 | } 71 | 72 | void LittleAreaListWindow::oneLine(int i) 73 | { 74 | mm.areaList->gotoActive(position + i + disp); 75 | sprintf(list->lineBuf, "%c%-50.50s ", (!mm.areaList->isShortlist() 76 | && (mm.areaList->getType() & ACTIVE)) ? '*' : ' ', 77 | mm.areaList->getDescription()); 78 | areaconv_in(list->lineBuf); 79 | DrawOne(i, C_LALLINES); 80 | } 81 | 82 | searchret LittleAreaListWindow::oneSearch(int x, const char *item, int) 83 | { 84 | mm.areaList->gotoActive(x + disp); 85 | return mm.areaList->filterCheck(item) ? True : False; 86 | } 87 | 88 | bool LittleAreaListWindow::extrakeys(int key) 89 | { 90 | switch (key) { 91 | case MM_ENTER: 92 | Select(); 93 | if (mm.areaList->getType() & (COLLECTION | READONLY)) { 94 | areanum = -1; 95 | ui.nonFatalError("Cannot reply there"); 96 | } else 97 | areanum = mm.areaList->getAreaNo(); 98 | break; 99 | case 'L': 100 | Select(); 101 | { 102 | int x = mm.areaList->getAreaNo(); 103 | 104 | ui.areas.Select(); 105 | mm.areaList->relist(); 106 | ui.areas.ResetActive(); 107 | 108 | mm.areaList->gotoArea(x); 109 | active = mm.areaList->getActive() - disp; 110 | } 111 | ui.redraw(); 112 | } 113 | return false; 114 | } 115 | 116 | void LittleAreaListWindow::setFilter(const char *item) 117 | { 118 | mm.areaList->setFilter(item); 119 | init(); 120 | } 121 | 122 | // ***** AreaListWindow ***** 123 | 124 | void AreaListWindow::FirstUnread() 125 | { 126 | int i; 127 | 128 | mm.areaList->updatePers(); 129 | 130 | position = active = 0; 131 | for (i = 0; i < NumOfItems(); i++) { 132 | mm.areaList->gotoActive(i); 133 | if (!mm.areaList->getNoOfUnread()) 134 | Move(KEY_DOWN); 135 | else 136 | break; 137 | } 138 | if (i == NumOfItems()) { 139 | position = active = 0; 140 | for (i = 0; i < NumOfItems(); i++) { 141 | mm.areaList->gotoActive(i); 142 | if (!mm.areaList->getNoOfLetters()) 143 | Move(KEY_DOWN); 144 | else 145 | break; 146 | } 147 | } 148 | } 149 | 150 | int AreaListWindow::NumOfItems() 151 | { 152 | return mm.areaList->noOfActive(); 153 | } 154 | 155 | void AreaListWindow::oneLine(int i) 156 | { 157 | char *p = list->lineBuf; 158 | 159 | mm.areaList->gotoActive(position + i); 160 | 161 | unsigned long attrib = mm.areaList->getType(); 162 | 163 | if (position + i == active) { 164 | p = strnzcpy(p, mm.areaList->getAreaType(), 20); 165 | 166 | if (mm.areaList->isNetmail()) 167 | p += sprintf(p, ", Netmail"); 168 | else 169 | if (mm.areaList->isInternet()) 170 | p += sprintf(p, ", Email"); 171 | else 172 | if (mm.areaList->isUsenet()) 173 | p += sprintf(p, ", Usenet"); 174 | else 175 | if (attrib & ECHOAREA) 176 | p += sprintf(p, ", Echo"); 177 | 178 | if (attrib & PERSONLY) 179 | p += sprintf(p, ", Pers"); 180 | else 181 | if (attrib & PERSALL) 182 | p += sprintf(p, ", Pers+All"); 183 | 184 | int q = ((list_max_x >> 1) - 8) - (p - list->lineBuf); 185 | while (--q > 0) 186 | sprintf(p++, " "); 187 | p = list->lineBuf; 188 | 189 | list->attrib(C_ALINFOTEXT2); 190 | list->put(list_max_y + 3 + hasSys, 8, p); 191 | 192 | list->delay_update(); 193 | } 194 | p += sprintf(p, format, ((attrib & ADDED) ? '+' : 195 | ((attrib & DROPPED) ? '-' : ((attrib & ACTIVE) && 196 | !mm.areaList->isShortlist()) ? '*' : 197 | ((attrib & HASREPLY) ? 'R' : ' '))), 198 | mm.areaList->getShortName(), mm.areaList->getDescription()); 199 | 200 | if (mm.areaList->getNoOfLetters()) 201 | p += sprintf(p, " %6d ", mm.areaList->getNoOfLetters()); 202 | else 203 | p += sprintf(p, " . "); 204 | 205 | if (mm.areaList->getNoOfUnread()) 206 | p += sprintf(p, "%6d ", mm.areaList->getNoOfUnread()); 207 | else 208 | p += sprintf(p, " . "); 209 | 210 | if (hasPers) { 211 | if (mm.areaList->getNoOfPersonal()) 212 | sprintf(p, "%6d ", mm.areaList->getNoOfPersonal()); 213 | else 214 | sprintf(p, " . "); 215 | } 216 | coltype ch = ((attrib & (REPLYAREA | ADDED | DROPPED)) || 217 | ((attrib & HASREPLY) && !(attrib & ACTIVE))) ? 218 | C_ALREPLINE : C_ALPACKETLINE; 219 | 220 | areaconv_in(list->lineBuf); 221 | DrawOne(i, mm.areaList->getNoOfUnread() ? emph(ch) : noemph(ch)); 222 | } 223 | 224 | searchret AreaListWindow::oneSearch(int x, const char *item, int mode) 225 | { 226 | searchret retval; 227 | 228 | mm.areaList->gotoActive(x); 229 | retval = mm.areaList->filterCheck(item) ? True : False; 230 | 231 | if (!retval && (mode < s_arealist) && mm.areaList->getNoOfLetters()) { 232 | int oldactive = active; 233 | ResetActive(); 234 | mm.areaList->getLetterList(); 235 | mm.letterList->setMode(-1); 236 | mm.letterList->relist(); 237 | ui.changestate(letterlist); 238 | ui.letters.setActive(-1); 239 | retval = ui.letters.search(item, mode); 240 | if (retval != True) { 241 | active = oldactive; 242 | ui.changestate(arealist); 243 | delete mm.letterList; 244 | } 245 | } 246 | 247 | return retval; 248 | } 249 | 250 | void AreaListWindow::Select() 251 | { 252 | mm.areaList->gotoActive(active); 253 | } 254 | 255 | void AreaListWindow::ResetActive() 256 | { 257 | active = mm.areaList->getActive(); 258 | } 259 | 260 | void AreaListWindow::MakeActive() 261 | { 262 | static const char *almodes[] = {"All", "Subscribed", "Active"}; 263 | unsigned int padding, middle; 264 | char tmp[80], tpad[7]; 265 | 266 | hasPers = mm.packet->hasPersonal(); 267 | mm.areaList->updatePers(); 268 | 269 | mm.areaList->setMode(mm.areaList->getMode() - 1); 270 | mm.areaList->relist(); 271 | 272 | const char *bb = mm.packet->getBBSName(); 273 | const char *sy = mm.packet->getSysOpName(); 274 | const char *bp = mm.packet->getBBSProg(); 275 | const char *dp = mm.packet->getDoorProg(); 276 | 277 | hasSys = bb && *bb; 278 | bool hasProg = (bp && *bp) || (dp && *dp); 279 | 280 | list_max_y = LINES - (mm.res.getInt(ExpertMode) ? 11 : 15) 281 | + !hasSys + !hasProg; 282 | list_max_x = COLS - 6; 283 | top_offset = 2; 284 | 285 | const char *filter = mm.areaList->getFilter(); 286 | 287 | char *p = tmp + sprintf(tmp, "%.20s | %s Areas", 288 | mm.res.get(PacketName), 289 | almodes[mm.areaList->getMode()]); 290 | 291 | if (NumOfItems() > list_max_y) 292 | p += sprintf(p, " (%d)", NumOfItems()); 293 | 294 | if (filter) 295 | sprintf(p, " | %.20s", filter); 296 | 297 | charconv_in(tmp); 298 | 299 | borderCol = C_ALBORDER; 300 | 301 | list = new InfoWin(list_max_y + 5 + hasSys + hasProg, list_max_x + 2, 302 | 2, borderCol, tmp, C_ALBTEXT, 5 + hasSys + hasProg); 303 | 304 | list->attrib(C_ALHEADTEXT); 305 | list->put(1, 3, "Area# Description"); 306 | int newloc = list_max_x - 14; 307 | if (hasPers) 308 | newloc -= 8; 309 | list->put(1, newloc, "Total Unread"); 310 | if (hasPers) 311 | list->put(1, list_max_x - 5, "Pers"); 312 | 313 | list->horizline(list_max_y + 2); 314 | 315 | padding = list_max_x - 26; 316 | if (hasPers) 317 | padding -= 8; 318 | sprintf(format, "%%c%%6s %%-%d.%ds", padding, padding); 319 | 320 | middle = (list_max_x - 2) >> 1; 321 | padding = list_max_x - 8; 322 | 323 | list->attrib(C_ALINFOTEXT); 324 | if (hasSys) 325 | list->put(list_max_y + 3, 2, "Name:"); 326 | 327 | if (sy && *sy) 328 | list->put(list_max_y + 3 + hasSys, middle, " Sysop:"); 329 | 330 | list->put(list_max_y + 3 + hasSys, 2, "Type:"); 331 | 332 | if (dp && *dp) 333 | list->put(list_max_y + 4 + hasSys, 2, "Door:"); 334 | 335 | if (bp && *bp) 336 | list->put(list_max_y + 4 + hasSys, middle, " BBS:"); 337 | 338 | sprintf(tpad, "%%.%ds", (middle < 87) ? middle - 8 : 79); 339 | middle += 8; 340 | 341 | list->attrib(C_ALINFOTEXT2); 342 | 343 | if (hasSys) { 344 | p = list->lineBuf; 345 | sprintf(p, "%-*.*s", padding, padding, bb); 346 | charconv_in(p); 347 | list->put(list_max_y + 3, 8, p); 348 | } 349 | 350 | if (sy && *sy) { 351 | sprintf(tmp, tpad, sy); 352 | charconv_in(tmp); 353 | list->put(list_max_y + 3 + hasSys, middle, tmp); 354 | } 355 | 356 | if (dp && *dp) { 357 | sprintf(tmp, tpad, dp); 358 | charconv_in(tmp); 359 | list->put(list_max_y + 4 + hasSys, 8, tmp); 360 | } 361 | 362 | if (bp && *bp) { 363 | sprintf(tmp, tpad, bp); 364 | charconv_in(tmp); 365 | list->put(list_max_y + 4 + hasSys, middle, tmp); 366 | } 367 | 368 | DrawAll(); 369 | Select(); 370 | } 371 | 372 | void AreaListWindow::Delete() 373 | { 374 | delete list; 375 | } 376 | 377 | void AreaListWindow::Prev() 378 | { 379 | do { 380 | Move(KEY_UP); 381 | Select(); 382 | } while (!mm.areaList->getNoOfLetters() && (mm.areaList->getActive() > 0)); 383 | } 384 | 385 | void AreaListWindow::Next() 386 | { 387 | do { 388 | Move(KEY_DOWN); 389 | Select(); 390 | } while (!mm.areaList->getNoOfLetters() && 391 | (mm.areaList->getActive() < mm.areaList->noOfActive() - 1)); 392 | } 393 | 394 | bool AreaListWindow::extrakeys(int key) 395 | { 396 | bool end = false; 397 | 398 | Select(); 399 | switch (key) { 400 | case 5: 401 | case 'E': 402 | if (!(mm.areaList->getType() & (COLLECTION | READONLY))) { 403 | if ((5 == key) || mm.areaList->isEmail()) { 404 | ui.addressbook(); 405 | Select(); 406 | } 407 | ui.letterwindow.EnterLetter(mm.areaList->getAreaNo(), 'E'); 408 | } else 409 | ui.nonFatalError("Cannot reply there"); 410 | break; 411 | #ifdef USE_MOUSE 412 | case MM_MOUSE: 413 | { 414 | int begx = list->xstart(), begy = list->ystart(); 415 | 416 | if ((mm_mouse_event.y != begy) || ((mm_mouse_event.x < (begx + 13)) 417 | || (mm_mouse_event.x > (begx + 40)))) 418 | break; 419 | } 420 | #endif 421 | case 'L': 422 | mm.areaList->relist(); 423 | ResetActive(); 424 | ui.redraw(); 425 | break; 426 | case 'S': 427 | case MM_INS: 428 | case 'U': 429 | case MM_DEL: 430 | if (mm.areaList->hasOffConfig()) { 431 | switch (key) { 432 | case 'S': 433 | case MM_INS: 434 | mm.areaList->Add(); 435 | break; 436 | default: 437 | mm.areaList->Drop(); 438 | } 439 | ui.setUnsavedNoAuto(); 440 | Move(KEY_DOWN); 441 | Draw(); 442 | } else 443 | ui.nonFatalError("Offline config is unavailable"); 444 | } 445 | return end; 446 | } 447 | 448 | void AreaListWindow::setFilter(const char *item) 449 | { 450 | mm.areaList->setFilter(item); 451 | } 452 | -------------------------------------------------------------------------------- /interfac/mysystem.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * some low-level routines common to both sides 4 | 5 | Copyright 1997-2021 William McBrine 6 | Distributed under the GNU General Public License, version 3 or later. */ 7 | 8 | /* Most non-ANSI, non-curses stuff is here. */ 9 | 10 | #include "interfac.h" 11 | #include "error.h" 12 | 13 | extern "C" { 14 | #if defined(__WATCOMC__) || defined(_MSC_VER) 15 | # include 16 | #else 17 | # ifndef USE_FINDFIRST 18 | # include 19 | # endif 20 | #endif 21 | 22 | #ifdef USE_SPAWNO 23 | # include 24 | #endif 25 | 26 | #ifdef LIMIT_MEM 27 | # ifdef __WATCOMC__ 28 | # include 29 | # else 30 | # include 31 | # endif 32 | #endif 33 | 34 | #ifdef HAS_UNISTD 35 | # include 36 | #endif 37 | 38 | #ifdef USE_DIRH 39 | # include 40 | #endif 41 | 42 | #include 43 | 44 | #ifdef HAS_UNAME 45 | # include 46 | #endif 47 | 48 | #ifdef USE_SETFTIME 49 | # include 50 | #else 51 | # if defined(__WATCOMC__) || defined(_MSC_VER) 52 | # include 53 | # else 54 | # include 55 | # endif 56 | #endif 57 | 58 | #if defined(USE_IOH) || defined(USE_SETFTIME) 59 | # include 60 | #endif 61 | 62 | #if defined(__WATCOMC__) || defined(TURBO16) 63 | # include 64 | #endif 65 | 66 | #if defined(USE_FINDFIRST) && defined(USE_IOH) 67 | # include 68 | #endif 69 | } 70 | 71 | #ifndef S_IREAD 72 | # define S_IREAD S_IRUSR 73 | #endif 74 | 75 | #ifndef S_IWRITE 76 | # define S_IWRITE S_IWUSR 77 | #endif 78 | 79 | #ifndef USE_FINDFIRST 80 | static DIR *Dir; 81 | #endif 82 | 83 | void fatalError(const char *description); 84 | 85 | char *myfgets(char *s, int size, FILE *stream) 86 | { 87 | char *end = 0; 88 | 89 | if (!feof(stream) && fgets(s, size, stream)) { 90 | 91 | end = s + strlen(s) - 1; 92 | 93 | // Skip any leftovers: 94 | if (*end != '\n') 95 | while (!feof(stream) && (fgetc(stream) != '\n')); 96 | 97 | } 98 | return end; 99 | } 100 | 101 | int mysystem(const char *cmd) 102 | { 103 | if (ui.on && !isendwin()) 104 | endwin(); 105 | 106 | #ifdef USE_SPAWNO 107 | int result = mm.res.getInt(swapOut) ? 108 | systemo(mm.res.get(BaseDir), cmd) : -1; 109 | 110 | if (-1 == result) 111 | result = system(cmd); 112 | #else 113 | int result = system(cmd); 114 | #endif 115 | 116 | // Non-zero result = error; pause so it can (maybe) be read 117 | if (result) 118 | napms(2000); 119 | 120 | if (ui.on) { 121 | #ifdef PDCURSES 122 | PDC_set_title(MM_NAME); 123 | #endif 124 | keypad(stdscr, TRUE); 125 | } 126 | 127 | return result; 128 | } 129 | 130 | int mysystem2(const char *cmd, const char *args) 131 | { 132 | size_t lencmd = strlen(cmd); 133 | char *qargs = canonize(quotespace(args)); 134 | char *cmdline = new char[lencmd + strlen(qargs) + 2]; 135 | 136 | sprintf(cmdline, "%s %s", cmd, qargs); 137 | 138 | int result = mysystem(cmdline); 139 | 140 | delete[] cmdline; 141 | delete[] qargs; 142 | return result; 143 | } 144 | 145 | char *mytmpdir(const char *home) 146 | { 147 | mystat st; 148 | char name[9]; 149 | 150 | if (mychdir(home)) 151 | fatalError("Could not change to temp dir"); 152 | 153 | do 154 | sprintf(name, "work%04x", (rand() & 0xffff)); 155 | while (st.init(name)); 156 | 157 | return canonize(fullpath(home, name)); 158 | } 159 | 160 | char *mytmpnam() 161 | { 162 | static long tcount = 1; 163 | char name[13]; 164 | 165 | if (tcount > 99999L) 166 | fatalError("Out of temporary filenames"); 167 | 168 | sprintf(name, "tmp%05ld.txt", tcount++); 169 | 170 | return canonize(fullpath(mm.res.get(BaseDir), name)); 171 | } 172 | 173 | void edit(const char *reply_filename) 174 | { 175 | mysystem2(mm.res.get(editor), reply_filename); 176 | } 177 | 178 | #ifdef __WATCOMC__ 179 | void setdisk(int drive) 180 | { 181 | unsigned total; /* We don't care, but we have to feed it. */ 182 | 183 | _dos_setdrive((unsigned) ++drive, &total); 184 | } 185 | #endif 186 | 187 | int mychdir(const char *pathname) 188 | { 189 | #ifdef USE_SETDISK 190 | if (':' == pathname[1]) 191 | setdisk(toupper(pathname[0]) - 'A'); 192 | #endif 193 | return chdir(pathname); 194 | } 195 | 196 | int mymkdir(const char *pathname) 197 | { 198 | #ifdef HAS_UNISTD 199 | return mkdir(pathname, S_IRWXU); 200 | #else 201 | return mkdir(pathname); 202 | #endif 203 | } 204 | 205 | void myrmdir(const char *pathname) 206 | { 207 | rmdir(pathname); 208 | } 209 | 210 | char *mygetcwd() 211 | { 212 | char pathname[256], *result; 213 | result = getcwd(pathname, 255); 214 | return strdupplus(result ? pathname : "."); 215 | } 216 | 217 | // system name -- results of uname() 218 | const char *sysname() 219 | { 220 | #if defined(__WIN32__) 221 | return "Win"; 222 | #elif defined(__APPLE__) 223 | return "Mac"; 224 | #elif defined(__MSDOS__) 225 | # ifdef SIXTEENBIT 226 | return "XT"; 227 | # else 228 | return "DOS"; 229 | # endif 230 | #elif defined(__OS2__) 231 | return "OS2"; 232 | #elif defined(HAS_UNAME) 233 | static struct utsname buf; 234 | 235 | if (!buf.sysname[0]) 236 | uname(&buf); 237 | 238 | return buf.sysname; 239 | #else 240 | return "?"; 241 | #endif 242 | } 243 | 244 | bool myopendir(const char *dirname) 245 | { 246 | #ifdef USE_FINDFIRST 247 | return !mychdir(dirname); 248 | #else 249 | return ((Dir = opendir((char *) dirname)) != 0) ? 250 | !mychdir(dirname) : false; 251 | #endif 252 | } 253 | 254 | const char *myreaddir(mystat &st) 255 | { 256 | #ifdef USE_FINDFIRST 257 | # ifdef USE_IOH // Windows 258 | static intptr_t handle; 259 | static bool first = true; 260 | static struct _finddata_t blk; 261 | int result; 262 | 263 | if (first) { 264 | char wildcard[] = "*"; 265 | handle = _findfirst(wildcard, &blk); 266 | result = (handle == -1) ? -1 : 0; 267 | first = false; 268 | } else 269 | result = _findnext(handle, &blk); 270 | 271 | if (-1 == result) { 272 | if (-1 != handle) 273 | _findclose(handle); 274 | 275 | first = true; 276 | return 0; 277 | } else { 278 | st.init((long) blk.size, blk.time_write, blk.attrib); 279 | return blk.name; 280 | } 281 | # else // DOS(ish) 282 | static struct ffblk blk; 283 | static bool first = true; 284 | int result; 285 | 286 | if (first) { 287 | result = findfirst("*.*", &blk, FA_DIREC); 288 | first = false; 289 | } else 290 | result = findnext(&blk); 291 | 292 | if (result) { 293 | # ifndef __MSDOS__ 294 | findclose(&blk); 295 | # endif 296 | first = true; 297 | return 0; 298 | } else { 299 | st.init(blk.ff_fsize, ((long) blk.ff_ftime << 16) + 300 | (long) blk.ff_fdate, blk.ff_attrib); 301 | return blk.ff_name; 302 | } 303 | # endif 304 | #else // POSIX 305 | static dirent *entry; 306 | const char *result = 0; 307 | 308 | entry = readdir(Dir); 309 | if (entry) 310 | result = entry->d_name; 311 | else 312 | closedir(Dir); 313 | 314 | if (result) 315 | st.init(result); 316 | return result; 317 | #endif 318 | } 319 | 320 | void clearDirectory(const char *DirName) 321 | { 322 | mystat st; 323 | const char *fname; 324 | 325 | if (myopendir(DirName)) { 326 | while ((fname = myreaddir(st)) != 0) 327 | if (!st.isdir()) 328 | remove(fname); 329 | } else { 330 | char tmp[512]; 331 | sprintf(tmp, "Could not change to %.491s", DirName); 332 | fatalError(tmp); 333 | } 334 | } 335 | 336 | #ifdef USE_SETFTIME 337 | void myutime(const char *fname, time_t now) 338 | { 339 | struct ftime ut; 340 | struct tm tmnow = *localtime(&now); 341 | 342 | ut.ft_tsec = tmnow.tm_sec >> 1; 343 | ut.ft_min = tmnow.tm_min; 344 | ut.ft_hour = tmnow.tm_hour; 345 | ut.ft_day = tmnow.tm_mday; 346 | ut.ft_month = tmnow.tm_mon + 1; 347 | ut.ft_year = tmnow.tm_year - 80; 348 | 349 | int f = open(fname, O_RDWR | O_BINARY); 350 | if (f != -1) { 351 | setftime(f, &ut); 352 | close(f); 353 | } 354 | } 355 | #endif 356 | 357 | time_t touchFile(const char *fname) 358 | { 359 | time_t now = time(0); 360 | #ifdef USE_SETFTIME 361 | myutime(fname, now); 362 | #else 363 | struct utimbuf ut; 364 | ut.actime = ut.modtime = now; 365 | utime((char *) fname, &ut); 366 | #endif 367 | return now; 368 | } 369 | 370 | #ifdef LIMIT_MEM 371 | 372 | /* Constrain memory allocation according to maximum block size and free 373 | memory remaining. Currently used only in the 16-bit MS-DOS port. 374 | */ 375 | 376 | long maxfreemem() 377 | { 378 | return 379 | # ifdef __WATCOMC__ 380 | (long) _memmax(); 381 | # else // Turbo C++ 382 | (long) coreleft(); 383 | # endif 384 | } 385 | 386 | long limitmem(long wanted) 387 | { 388 | long maxavail = maxfreemem(); 389 | 390 | // Give it a 25% margin 391 | maxavail -= (wanted >> 2); 392 | 393 | if (wanted > maxavail) 394 | wanted = maxavail; 395 | 396 | return wanted; 397 | } 398 | 399 | #endif 400 | 401 | /* Convert pathnames to "canonical" form (change slashes to backslashes). 402 | The "nospace" stuff leaves any parameters unconverted. 403 | */ 404 | 405 | char *canonize(char *sinner) 406 | { 407 | #ifdef DOSNAMES 408 | int i; 409 | bool nospace = true; 410 | bool inquotes = false; 411 | 412 | for (i = 0; sinner[i] && nospace; i++) { 413 | if ('\"' == sinner[i]) 414 | inquotes = !inquotes; 415 | else 416 | if ('/' == sinner[i]) 417 | sinner[i] = '\\'; 418 | else 419 | if ((' ' == sinner[i]) && !inquotes) 420 | nospace = false; 421 | } 422 | #endif 423 | return sinner; 424 | } 425 | 426 | #ifdef HAS_HOME 427 | 428 | /* Recognize '~' as a substitute for the home directory path, on Unix-like 429 | systems. 430 | */ 431 | 432 | const char *homify(const char *raw) 433 | { 434 | static const char *home = getenv("HOME"); 435 | 436 | if (home && raw && (raw[0] == '~') && 437 | ((raw[1] == '/') || (raw[1] == '\0'))) { 438 | 439 | static char expanded[512]; 440 | 441 | sprintf(expanded, "%.255s/%.255s", home, raw + 1); 442 | return expanded; 443 | } else 444 | return raw; 445 | } 446 | 447 | #endif 448 | 449 | #ifdef USE_SHELL 450 | 451 | /* Command shell routine -- currently only used in the DOSish ports */ 452 | 453 | Shell::Shell() 454 | { 455 | const char *oldprompt = getenv("PROMPT"); 456 | if (!oldprompt) 457 | oldprompt = "$p$g"; 458 | 459 | size_t len = strlen(oldprompt) + 13; 460 | prompt = new char[len]; 461 | 462 | sprintf(prompt, "PROMPT=%s[MM] ", oldprompt); 463 | putenv(prompt); 464 | } 465 | 466 | Shell::~Shell() 467 | { 468 | delete[] prompt; 469 | } 470 | 471 | void Shell::out() 472 | { 473 | mychdir(error.getOrigDir()); 474 | touchwin(stdscr); 475 | refresh(); 476 | mysystem(getenv("COMSPEC")); 477 | 478 | ui.redraw(); 479 | } 480 | 481 | #endif 482 | 483 | #ifdef EXTRAPATH 484 | 485 | /* Add the starting directory and the MMAIL directory to the PATH, 486 | mainly for the benefit of Windows, where InfoZip is not standard. 487 | (But currently this is enabled for all the DOSish ports.) 488 | */ 489 | ExtraPath::ExtraPath() 490 | { 491 | const char *oldpath = getenv("PATH"); 492 | if (!oldpath) 493 | fatalError("No PATH defined!"); 494 | 495 | const char *orig = error.getOrigDir(); 496 | const char *home = mm.res.get(homeDir); 497 | 498 | size_t len = strlen(oldpath) + strlen(orig) + strlen(home) + 8; 499 | newpath = new char[len]; 500 | 501 | sprintf(newpath, "PATH=%s;%s;%s", oldpath, orig, home); 502 | putenv(newpath); 503 | } 504 | 505 | ExtraPath::~ExtraPath() 506 | { 507 | delete[] newpath; 508 | } 509 | 510 | #endif 511 | 512 | mystat::mystat(const char *fname) 513 | { 514 | init(fname); 515 | } 516 | 517 | mystat::mystat() 518 | { 519 | init(); 520 | } 521 | 522 | bool mystat::init(const char *fname) 523 | { 524 | #ifdef USE_FINDFIRST 525 | # ifdef USE_IOH // Windows 526 | struct _finddata_t blk; 527 | long result = _findfirst((char *) fname, &blk); 528 | bool retval = (-1 != result); 529 | 530 | if (retval) { 531 | init((long) blk.size, blk.time_write, blk.attrib); 532 | _findclose(result); 533 | } else 534 | init(); 535 | 536 | # else // DOS(ish) 537 | struct ffblk blk; 538 | bool retval = !findfirst(fname, &blk, FA_DIREC); 539 | 540 | if (retval) 541 | init(blk.ff_fsize, ((long) blk.ff_ftime << 16) + 542 | (long) blk.ff_fdate, blk.ff_attrib); 543 | else 544 | init(); 545 | 546 | # ifndef __MSDOS__ 547 | findclose(&blk); 548 | # endif 549 | # endif 550 | #else // POSIX 551 | struct stat fileStat; 552 | bool retval = !stat((char *) fname, &fileStat); 553 | 554 | if (retval) { 555 | size = fileStat.st_size; 556 | date = fileStat.st_mtime; 557 | mode = fileStat.st_mode; 558 | } else 559 | init(); 560 | #endif 561 | return retval; 562 | } 563 | 564 | #ifdef USE_FINDFIRST 565 | # ifdef USE_IOH 566 | 567 | void mystat::init(long sizeA, time_t dateA, unsigned attrib) 568 | { 569 | size = sizeA; 570 | date = dateA; 571 | mode = S_IREAD | ((attrib & _A_RDONLY) ? 0 : S_IWRITE) | 572 | ((attrib & _A_SUBDIR) ? S_IFDIR : 0); 573 | } 574 | 575 | # else 576 | 577 | void mystat::init(long sizeA, long dateA, char ff_attrib) 578 | { 579 | size = sizeA; 580 | date = mktime(getdostime(dateA)); 581 | mode = S_IREAD | ((ff_attrib & FA_RDONLY) ? 0 : S_IWRITE) | 582 | ((ff_attrib & FA_DIREC) ? S_IFDIR : 0); 583 | } 584 | 585 | # endif 586 | #endif 587 | 588 | void mystat::init() 589 | { 590 | size = -1; 591 | date = (time_t) -1; 592 | mode = 0; 593 | } 594 | 595 | bool mystat::isdir() 596 | { 597 | return !(!(mode & S_IFDIR)); 598 | } 599 | 600 | bool mystat::readable() 601 | { 602 | return !(!(mode & S_IREAD)); 603 | } 604 | 605 | bool mystat::writeable() 606 | { 607 | return !(!(mode & S_IWRITE)); 608 | } 609 | 610 | off_t mystat::fsize() 611 | { 612 | return size; 613 | } 614 | 615 | time_t mystat::fdate() 616 | { 617 | return date; 618 | } 619 | 620 | void mystat::reset_date(const char *fname) 621 | { 622 | if (date != (time_t) -1) 623 | #ifdef USE_SETFTIME 624 | myutime(fname, date); 625 | #else 626 | { 627 | struct utimbuf ut; 628 | ut.actime = date; // Should be current time 629 | ut.modtime = date; 630 | utime((char *) fname, &ut); 631 | } 632 | #endif 633 | } 634 | -------------------------------------------------------------------------------- /mmail/area.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * MultiMail offline mail reader 3 | * area_header and area_list 4 | 5 | Copyright 1996-1997 Toth Istvan 6 | Copyright 1998-2021 William McBrine 7 | Distributed under the GNU General Public License, version 3 or later. */ 8 | 9 | #include "mmail.h" 10 | 11 | // ----------------------------------------------------------------- 12 | // Area header methods 13 | // ----------------------------------------------------------------- 14 | 15 | area_header::area_header(int numA, const char *shortNameA, 16 | const char *nameA, const char *descriptionA, const char *areaTypeA, 17 | unsigned long typeA, int noOfLettersA, int noOfPersonalA, 18 | int maxtolenA, int maxsublenA) : shortName(shortNameA), 19 | name(nameA), description(descriptionA), areaType(areaTypeA), 20 | type(typeA), noOfLetters(noOfLettersA), noOfPersonal(noOfPersonalA), 21 | maxtolen(maxtolenA), maxsublen(maxsublenA) 22 | { 23 | noOfReplies = 0; 24 | 25 | driver = mm.getDriver(numA); 26 | num = numA - mm.getOffset(driver); 27 | } 28 | 29 | const char *area_header::getName() const 30 | { 31 | return name; 32 | } 33 | 34 | const char *area_header::getShortName() const 35 | { 36 | return shortName; 37 | } 38 | 39 | const char *area_header::getDescription() const 40 | { 41 | return description; 42 | } 43 | 44 | const char *area_header::getTear() 45 | { 46 | return driver->getTear(num); 47 | } 48 | 49 | const char *area_header::getAreaType() const 50 | { 51 | return areaType; 52 | } 53 | 54 | unsigned long area_header::getType() const 55 | { 56 | return type; 57 | } 58 | 59 | int area_header::getNoOfLetters() const 60 | { 61 | return noOfLetters; 62 | } 63 | 64 | int area_header::getNoOfUnread() 65 | { 66 | return (mm.getReadObject(driver))->getNoOfUnread(num); 67 | } 68 | 69 | int area_header::getNoOfMarked() 70 | { 71 | return (mm.getReadObject(driver))->getNoOfMarked(num); 72 | } 73 | 74 | int area_header::getNoOfPersonal() const 75 | { 76 | return noOfPersonal; 77 | } 78 | 79 | bool area_header::getUseAlias() const 80 | { 81 | return !(!(type & ALIAS)); 82 | } 83 | 84 | bool area_header::isCollection() const 85 | { 86 | return !(!(type & COLLECTION)); 87 | } 88 | 89 | bool area_header::isReplyArea() const 90 | { 91 | return !(!(type & REPLYAREA)); 92 | } 93 | 94 | bool area_header::isActive() const 95 | { 96 | return !(!(type & (ACTIVE | ADDED | DROPPED | HASREPLY))); 97 | } 98 | 99 | bool area_header::isNetmail() const 100 | { 101 | return (type & NETMAIL) && !(type & INTERNET); 102 | } 103 | 104 | bool area_header::isInternet() const 105 | { 106 | return (type & NETMAIL) && (type & INTERNET); 107 | } 108 | 109 | bool area_header::isEmail() const 110 | { 111 | return !(!(type & NETMAIL)); 112 | } 113 | 114 | bool area_header::isUsenet() const 115 | { 116 | return !(type & NETMAIL) && (type & INTERNET); 117 | } 118 | 119 | bool area_header::isLatin() const 120 | { 121 | return !(!(type & LATINCHAR)); 122 | } 123 | 124 | bool area_header::isReadOnly() const 125 | { 126 | return !(!(type & READONLY)); 127 | } 128 | 129 | bool area_header::hasTo() const 130 | { 131 | return !((type & INTERNET) && !(type & NETMAIL)); 132 | } 133 | 134 | bool area_header::hasPublic() const 135 | { 136 | return !(!(type & PUBLIC)); 137 | } 138 | 139 | bool area_header::hasPrivate() const 140 | { 141 | return !(!(type & PRIVATE)); 142 | } 143 | 144 | int area_header::maxToLen() const 145 | { 146 | return maxtolen; 147 | } 148 | 149 | int area_header::maxSubLen() const 150 | { 151 | return maxsublen; 152 | } 153 | 154 | bool area_header::hasOffConfig() const 155 | { 156 | return !(!(type & OFFCONFIG)); 157 | } 158 | 159 | void area_header::Add() 160 | { 161 | if (!(type & COLLECTION)) { 162 | if (type & DROPPED) 163 | type &= ~DROPPED; 164 | else 165 | if (!(type & ACTIVE) || !(type & SUBKNOWN)) 166 | type |= ADDED; 167 | } 168 | } 169 | 170 | void area_header::Drop() 171 | { 172 | if (!(type & COLLECTION)) { 173 | if (type & ADDED) 174 | type &= ~ADDED; 175 | else 176 | if ((type & ACTIVE) || !(type & SUBKNOWN)) 177 | type |= DROPPED; 178 | } 179 | } 180 | 181 | void area_header::addReply() 182 | { 183 | type |= HASREPLY; 184 | noOfReplies++; 185 | } 186 | 187 | void area_header::killReply() 188 | { 189 | noOfReplies--; 190 | if (!noOfReplies) 191 | type &= ~HASREPLY; 192 | } 193 | 194 | // ----------------------------------------------------------------- 195 | // Arealist methods 196 | // ----------------------------------------------------------------- 197 | 198 | area_list::area_list() 199 | { 200 | no = mm.packet->getNoOfAreas() + 1; 201 | filter = 0; 202 | 203 | activeHeader = new int[no]; 204 | areaHeader = new area_header *[no]; 205 | 206 | specific_driver *actDriver; 207 | for (int c = 0; c < no; c++) { 208 | actDriver = mm.getDriver(c); 209 | areaHeader[c] = actDriver->getNextArea(); 210 | } 211 | 212 | current = 0; 213 | almode = mm.res.getInt(AreaMode) - 1; 214 | relist(); 215 | 216 | // 1. Find out what types of areas we have (i.e. qwk, usenet... ) 217 | // 2. Create the appropriate driver objects 218 | // 3. Find out the number of areas for each type 219 | // 4. Allocate the memory for the area_header descriptions 220 | // 5. Fill the area headers 221 | } 222 | 223 | area_list::~area_list() 224 | { 225 | while (no) 226 | delete areaHeader[--no]; 227 | delete[] areaHeader; 228 | delete[] activeHeader; 229 | delete[] filter; 230 | } 231 | 232 | bool area_list::relist() 233 | { 234 | bool anyfound = !filter; 235 | noActive = 0; 236 | 237 | almode++; 238 | if (almode == 3) 239 | almode = 0; 240 | 241 | // Check if Active/Subscribed distincion makes sense -- checks 242 | // the last area, instead of making a global per-packet check; 243 | // bogus, but it works, because this value is always the same for 244 | // each area in a packet: 245 | 246 | if ((almode == 1) && !(areaHeader[no - 1]->getType() & SUBKNOWN)) 247 | almode++; 248 | 249 | int c = current; 250 | 251 | for (current = 0; current < no; current++) 252 | if ( ((current == REPLY_AREA) || (getType() & HASREPLY)) || 253 | ( (!filter || filterCheck(filter)) && ((almode == 0) || 254 | ((almode == 1) && areaHeader[current]->isActive()) || 255 | ((almode == 2) && getNoOfLetters())) ) ) { 256 | 257 | activeHeader[noActive++] = current; 258 | if (!anyfound) 259 | anyfound = (filterCheck(filter) != 0); 260 | } 261 | 262 | current = c; 263 | 264 | return anyfound; 265 | } 266 | 267 | int area_list::getRepList() 268 | { 269 | current = REPLY_AREA; 270 | getLetterList(); 271 | 272 | int max = mm.letterList->noOfLetter(); 273 | for (int x = 0; x < max; x++) { 274 | mm.letterList->gotoLetter(x); 275 | int area = mm.letterList->getAreaID(); 276 | areaHeader[area]->addReply(); 277 | } 278 | 279 | delete mm.letterList; 280 | 281 | return max; 282 | } 283 | 284 | void area_list::updatePers() 285 | { 286 | // This routine makes some assumptions -- that there's at most one 287 | // PERS area, and that if present, it's the second area -- that 288 | // are valid as the program is currently written, but that are not 289 | // made elsewhere in this class. 290 | 291 | if (mm.packet->hasPersArea()) { 292 | int c = current; 293 | current = REPLY_AREA + 1; 294 | if (isCollection() && !isReplyArea()) { 295 | letter_list *ll = mm.letterList; 296 | getLetterList(); 297 | delete mm.letterList; 298 | mm.letterList = ll; 299 | } 300 | current = c; 301 | } 302 | } 303 | 304 | bool area_list::isShortlist() const 305 | { 306 | return !(!almode); 307 | } 308 | 309 | int area_list::getMode() const 310 | { 311 | return almode; 312 | } 313 | 314 | void area_list::setMode(int newmode) 315 | { 316 | almode = newmode; 317 | } 318 | 319 | const char *area_list::getShortName() const 320 | { 321 | return areaHeader[current]->getShortName(); 322 | } 323 | 324 | const char *area_list::getName() const 325 | { 326 | return areaHeader[current]->getName(); 327 | } 328 | 329 | const char *area_list::getName(int area) 330 | { 331 | if ((area < 0) || (area >= no)) 332 | fatalError("Internal error in area_list::getName"); 333 | return areaHeader[area]->getName(); 334 | } 335 | 336 | const char *area_list::getDescription() const 337 | { 338 | return areaHeader[current]->getDescription(); 339 | } 340 | 341 | const char *area_list::getDescription(int area) 342 | { 343 | if ((area < 0) || (area >= no)) 344 | fatalError("Internal error in area_list::getDescription"); 345 | return areaHeader[area]->getDescription(); 346 | } 347 | 348 | const char *area_list::getAreaType() const 349 | { 350 | return areaHeader[current]->getAreaType(); 351 | } 352 | 353 | const char *area_list::getTear() 354 | { 355 | return areaHeader[current]->getTear(); 356 | } 357 | 358 | unsigned long area_list::getType() const 359 | { 360 | return areaHeader[current]->getType(); 361 | } 362 | 363 | int area_list::getNoOfLetters() const 364 | { 365 | return areaHeader[current]->getNoOfLetters(); 366 | } 367 | 368 | int area_list::getNoOfUnread() const 369 | { 370 | return areaHeader[current]->getNoOfUnread(); 371 | } 372 | 373 | int area_list::getNoOfMarked() const 374 | { 375 | return areaHeader[current]->getNoOfMarked(); 376 | } 377 | 378 | int area_list::getNoOfPersonal() const 379 | { 380 | return areaHeader[current]->getNoOfPersonal(); 381 | } 382 | 383 | void area_list::getLetterList() 384 | { 385 | mm.letterList = new letter_list(current, getType()); 386 | } 387 | 388 | int area_list::noOfAreas() const 389 | { 390 | return no; 391 | } 392 | 393 | int area_list::noOfActive() const 394 | { 395 | return noActive; 396 | } 397 | 398 | void area_list::gotoArea(int currentA) 399 | { 400 | if ((currentA >= 0) && (currentA < no)) 401 | current = currentA; 402 | } 403 | 404 | void area_list::gotoActive(int activeA) 405 | { 406 | if ((activeA >= 0) && (activeA < noActive)) 407 | current = activeHeader[activeA]; 408 | } 409 | 410 | int area_list::getAreaNo() const 411 | { 412 | return current; 413 | } 414 | 415 | int area_list::getActive() 416 | { 417 | int c; 418 | 419 | for (c = 0; c < noActive; c++) 420 | if (activeHeader[c] >= current) 421 | break; 422 | return c; 423 | } 424 | 425 | void area_list::enterLetter(int areaNo, const char *from, const char *to, 426 | const char *subject, const char *replyID, const char *newsgrp, 427 | int replyTo, bool privat, net_address &netAddress, 428 | const char *filename, long length) 429 | { 430 | gotoArea(areaNo); 431 | areaHeader[current]->addReply(); 432 | 433 | letter_header newLetter(subject, to, from, "", replyID, 434 | replyTo, 0, 0, areaNo, privat, 0, mm.reply, 435 | netAddress, isLatin(), newsgrp); 436 | 437 | mm.reply->enterLetter(newLetter, filename, length); 438 | 439 | refreshArea(); 440 | } 441 | 442 | void area_list::killLetter(int areaNo, long letterNo) 443 | { 444 | areaHeader[areaNo]->killReply(); 445 | mm.reply->killLetter((int) letterNo); 446 | refreshArea(); 447 | } 448 | 449 | void area_list::refreshArea() 450 | { 451 | delete areaHeader[REPLY_AREA]; 452 | 453 | areaHeader[REPLY_AREA] = mm.reply->refreshArea(); 454 | if (current == REPLY_AREA) 455 | mm.letterList->rrefresh(); 456 | } 457 | 458 | bool area_list::getUseAlias() const 459 | { 460 | return areaHeader[current]->getUseAlias(); 461 | } 462 | 463 | bool area_list::isCollection() const 464 | { 465 | return areaHeader[current]->isCollection(); 466 | } 467 | 468 | bool area_list::isReplyArea() const 469 | { 470 | return areaHeader[current]->isReplyArea(); 471 | } 472 | 473 | bool area_list::isEmail() const 474 | { 475 | return areaHeader[current]->isEmail(); 476 | } 477 | 478 | bool area_list::isNetmail() const 479 | { 480 | return areaHeader[current]->isNetmail(); 481 | } 482 | 483 | int area_list::findNetmail() const 484 | { 485 | int c; 486 | 487 | for (c = 0; c < no; c++) 488 | if (areaHeader[c]->isNetmail()) 489 | break; 490 | 491 | return (c < no) ? c : -1; 492 | } 493 | 494 | bool area_list::isInternet() const 495 | { 496 | return areaHeader[current]->isInternet(); 497 | } 498 | 499 | int area_list::findInternet() const 500 | { 501 | int c; 502 | 503 | for (c = 0; c < no; c++) 504 | if (areaHeader[c]->isInternet()) 505 | break; 506 | 507 | return (c < no) ? c : -1; 508 | } 509 | 510 | bool area_list::isUsenet() const 511 | { 512 | return areaHeader[current]->isUsenet(); 513 | } 514 | 515 | bool area_list::isLatin() const 516 | { 517 | return areaHeader[current]->isLatin(); 518 | } 519 | 520 | bool area_list::isLatin(int area) 521 | { 522 | if ((area < 0) || (area >= no)) 523 | fatalError("Internal error in area_list::isLatin"); 524 | return areaHeader[area]->isLatin(); 525 | } 526 | 527 | bool area_list::isReadOnly() const 528 | { 529 | return areaHeader[current]->isReadOnly(); 530 | } 531 | 532 | bool area_list::hasTo() const 533 | { 534 | return areaHeader[current]->hasTo(); 535 | } 536 | 537 | bool area_list::hasPublic() const 538 | { 539 | return areaHeader[current]->hasPublic(); 540 | } 541 | 542 | bool area_list::hasPrivate() const 543 | { 544 | return areaHeader[current]->hasPrivate(); 545 | } 546 | 547 | int area_list::maxToLen() const 548 | { 549 | return areaHeader[current]->maxToLen(); 550 | } 551 | 552 | int area_list::maxSubLen() const 553 | { 554 | return areaHeader[current]->maxSubLen(); 555 | } 556 | 557 | bool area_list::hasOffConfig() const 558 | { 559 | return areaHeader[current]->hasOffConfig(); 560 | } 561 | 562 | void area_list::Add() 563 | { 564 | areaHeader[current]->Add(); 565 | } 566 | 567 | void area_list::Drop() 568 | { 569 | areaHeader[current]->Drop(); 570 | } 571 | 572 | bool area_list::anyChanged() const 573 | { 574 | for (int c = 0; c < no; c++) 575 | if (areaHeader[c]->getType() & (ADDED | DROPPED)) 576 | return true; 577 | return false; 578 | } 579 | 580 | const char *area_list::getFilter() const 581 | { 582 | return filter; 583 | } 584 | 585 | void area_list::setFilter(const char *newfilter) 586 | { 587 | delete[] filter; 588 | filter = (newfilter && *newfilter) ? strdupplus(newfilter) : 0; 589 | almode--; 590 | if (!relist()) { 591 | delete[] filter; 592 | filter = 0; 593 | almode--; 594 | relist(); 595 | } 596 | } 597 | 598 | const char *area_list::filterCheck(const char *item) 599 | { 600 | const char *s = searchstr(getShortName(), item); 601 | if (!s) { 602 | s = searchstr(getName(), item); 603 | if (!s) 604 | s = searchstr(getDescription(), item); 605 | } 606 | 607 | return s; 608 | } 609 | --------------------------------------------------------------------------------