1/*
2 * $XConsortium: tocutil.c,v 2.60 95/01/09 16:52:53 swick Exp $
3 * $XFree86: xc/programs/xmh/tocutil.c,v 3.3 2001/10/28 03:34:40 tsi Exp $
4 *
5 *
6 *			COPYRIGHT 1987, 1989
7 *		   DIGITAL EQUIPMENT CORPORATION
8 *		       MAYNARD, MASSACHUSETTS
9 *			ALL RIGHTS RESERVED.
10 *
11 * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND
12 * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION.
13 * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
14 * ANY PURPOSE.  IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
15 *
16 * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT
17 * RIGHTS, APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN
18 * ADDITION TO THAT SET FORTH ABOVE.
19 *
20 * Permission to use, copy, modify, and distribute this software and its
21 * documentation for any purpose and without fee is hereby granted, provided
22 * that the above copyright notice appear in all copies and that both that
23 * copyright notice and this permission notice appear in supporting
24 * documentation, and that the name of Digital Equipment Corporation not be
25 * used in advertising or publicity pertaining to distribution of the software
26 * without specific, written prior permission.
27 */
28
29/* tocutil.c -- internal routines for toc stuff. */
30
31#include "xmh.h"
32#include "toc.h"
33#include "tocutil.h"
34#include "tocintrnl.h"
35
36#ifdef X_NOT_POSIX
37extern long lseek();
38#endif
39
40Toc TUMalloc(void)
41{
42    Toc toc;
43    toc = XtNew(TocRec);
44    bzero((char *)toc, (int) sizeof(TocRec));
45    toc->msgs = (Msg *) NULL;
46    toc->seqlist = (Sequence *) NULL;
47    toc->validity = unknown;
48    return toc;
49}
50
51
52/* Returns TRUE if the scan file for the given toc is out of date. */
53
54int TUScanFileOutOfDate(Toc toc)
55{
56    return LastModifyDate(toc->path) > toc->lastreaddate;
57}
58
59
60/* Make sure the sequence menu entries correspond exactly to the sequences
61 * for this toc.
62 */
63
64void TUCheckSequenceMenu(Toc toc)
65{
66    Scrn	scrn;
67    register int i, n;
68    Arg		query_args[2];
69    const char 	*name;
70    Cardinal	j;
71    int		numChildren;
72    Widget	menu, item;
73    Button	button;
74    WidgetList	children;
75
76    static XtCallbackRec callbacks[] = {
77	{ DoSelectSequence,		(XtPointer) NULL},
78	{ (XtCallbackProc) NULL,	(XtPointer) NULL},
79    };
80    static Arg  args[] = {
81	{ XtNcallback,			(XtArgVal) callbacks},
82	{ XtNleftMargin, 		(XtArgVal) 18},
83    };
84
85    for (j=0; j < toc->num_scrns; j++) {
86	scrn = toc->scrn[j];
87
88	/* Find the sequence menu and the number of entries in it. */
89
90	name = MenuBoxButtons[XMH_SEQUENCE].button_name;
91	button = BBoxFindButtonNamed(scrn->mainbuttons, name);
92	menu = BBoxMenuOfButton(button);
93	XtSetArg(query_args[0], XtNnumChildren, &numChildren);
94	XtSetArg(query_args[1], XtNchildren, &children);
95	XtGetValues(menu, query_args, (Cardinal) 2);
96	n = MenuBoxButtons[XMH_SEQUENCE].num_entries;
97	if (strcmp(XtName(children[0]), "menuLabel") == 0)
98	    n++;
99
100	/* Erase the current check mark. */
101
102	for (i=(n-1); i < numChildren; i++)
103	    ToggleMenuItem(children[i], False);
104
105	/* Delete any entries which should be deleted. */
106
107	for (i=n; i < numChildren; i++)
108	    if (! TocGetSeqNamed(toc, XtName(children[i])))
109		XtDestroyWidget(children[i]);
110
111	/* Create any entries which should be created. */
112
113	callbacks[0].closure = (XtPointer) scrn;
114	for (i=1; i < toc->numsequences; i++)
115	    if (! XtNameToWidget(menu, toc->seqlist[i]->name))
116		XtCreateManagedWidget(toc->seqlist[i]->name, smeBSBObjectClass,
117				      menu, args, XtNumber(args));
118
119	/* Set the check mark. */
120
121	name = toc->viewedseq->name;
122	if ((item = XtNameToWidget(menu, name)) != NULL)
123	    ToggleMenuItem(item, True);
124    }
125    TocSetSelectedSequence(toc, toc->viewedseq);
126}
127
128
129void TUScanFileForToc(Toc toc)
130{
131    Scrn scrn;
132    char  **argv, str[100];
133    if (toc) {
134	TUGetFullFolderInfo(toc);
135	if (toc->num_scrns) scrn = toc->scrn[0];
136	else scrn = scrnList[0];
137
138	snprintf(str, sizeof(str), "Rescanning %s", toc->foldername);
139	ChangeLabel(scrn->toclabel, str);
140
141	argv = MakeArgv(5);
142	argv[0] = "scan";
143	argv[1] = TocMakeFolderName(toc);
144	argv[2] = "-width";
145	snprintf(str,  sizeof(str), "%d", app_resources.toc_width);
146	argv[3] = str;
147	argv[4] = "-noheader";
148	DoCommand(argv, (char *) NULL, toc->scanfile);
149	XtFree(argv[1]);
150	XtFree((char *) argv);
151
152	toc->needslabelupdate = True;
153	toc->validity = valid;
154	toc->curmsg = NULL;	/* Get cur msg somehow! %%% */
155    }
156}
157
158
159
160int TUGetMsgPosition(Toc toc, Msg msg)
161{
162    int msgid, h = 0, l, m;
163    char str[100];
164    static Boolean ordered = True;
165    msgid = msg->msgid;
166    if (ordered) {
167	l = 0;
168	h = toc->nummsgs - 1;
169	while (l < h - 1) {
170	    m = (l + h) / 2;
171	    if (toc->msgs[m]->msgid > msgid)
172		h = m;
173	    else
174		l = m;
175	}
176	if (toc->msgs[l] == msg) return l;
177	if (toc->msgs[h] == msg) return h;
178    }
179    ordered = False;
180    for (l = 0; l < toc->nummsgs; l++) {
181	if (msgid == toc->msgs[l]->msgid) return l;
182    }
183    snprintf(str, sizeof(str),
184             "TUGetMsgPosition search failed! hi=%d, lo=%d, msgid=%d",
185             h, l, msgid);
186    Punt(str);
187    return 0; /* Keep lint happy. */
188}
189
190
191void TUResetTocLabel(Scrn scrn)
192{
193    char str[500];
194    Toc toc;
195    if (scrn) {
196	toc = scrn->toc;
197	if (toc == NULL)
198	    (void) strcpy(str, " ");
199	else {
200	    if (toc->stopupdate) {
201		toc->needslabelupdate = TRUE;
202		return;
203	    }
204	    snprintf(str, sizeof(str), "%s:%s", toc->foldername,
205                     toc->viewedseq->name);
206	    toc->needslabelupdate = FALSE;
207	}
208	ChangeLabel((Widget) scrn->toclabel, str);
209    }
210}
211
212
213/* A major toc change has occurred; redisplay it.  (This also should work even
214   if we now have a new source to display stuff from.) */
215
216void TURedisplayToc(Scrn scrn)
217{
218    Toc toc;
219    Widget source;
220    if (scrn != NULL && scrn->tocwidget != NULL) {
221	toc = scrn->toc;
222 	if (toc) {
223	    if (toc->stopupdate) {
224		toc->needsrepaint = TRUE;
225		return;
226	    }
227	    XawTextDisableRedisplay(scrn->tocwidget);
228	    source = XawTextGetSource(scrn->tocwidget);
229	    if (toc->force_reset || source != toc->source) {
230		XawTextSetSource(scrn->tocwidget, toc->source,
231				 (XawTextPosition) 0);
232		toc->force_reset = False; /* %%% temporary */
233	    }
234	    TocSetCurMsg(toc, TocGetCurMsg(toc));
235	    XawTextEnableRedisplay(scrn->tocwidget);
236	    TUCheckSequenceMenu(toc);
237	    toc->needsrepaint = FALSE;
238	} else {
239	    XawTextSetSource(scrn->tocwidget, PNullSource, (XawTextPosition) 0);
240	}
241    }
242}
243
244
245void TULoadSeqLists(Toc toc)
246{
247    Sequence seq;
248    FILEPTR fid;
249    char    str[500], *ptr, *ptr2, viewed[500], selected[500];
250    int     i;
251    if (toc->viewedseq) (void) strcpy(viewed, toc->viewedseq->name);
252    else *viewed = 0;
253    if (toc->selectseq) (void) strcpy(selected, toc->selectseq->name);
254    else *selected = 0;
255    for (i = 0; i < toc->numsequences; i++) {
256	seq = toc->seqlist[i];
257	XtFree((char *) seq->name);
258	if (seq->mlist) FreeMsgList(seq->mlist);
259	XtFree((char *)seq);
260    }
261    toc->numsequences = 1;
262    toc->seqlist = (Sequence *) XtRealloc((char *) toc->seqlist,
263					  (Cardinal) sizeof(Sequence));
264    seq = toc->seqlist[0] = XtNew(SequenceRec);
265    seq->name = XtNewString("all");
266    seq->mlist = NULL;
267    toc->viewedseq = seq;
268    toc->selectseq = seq;
269    snprintf(str, sizeof(str), "%s/.mh_sequences", toc->path);
270    fid = myfopen(str, "r");
271    if (fid) {
272	while ((ptr = ReadLine(fid))) {
273	    ptr2 = strchr(ptr, ':');
274	    if (ptr2) {
275		*ptr2 = 0;
276		if (strcmp(ptr, "all") != 0 &&
277		    strcmp(ptr, "cur") != 0 &&
278		    strcmp(ptr, "unseen") != 0) {
279		    toc->numsequences++;
280		    toc->seqlist = XtReallocArray(toc->seqlist,
281                                       toc->numsequences, sizeof(Sequence));
282		    seq = toc->seqlist[toc->numsequences - 1] =
283			XtNew(SequenceRec);
284		    seq->name = XtNewString(ptr);
285		    seq->mlist = StringToMsgList(toc, ptr2 + 1);
286		    if (strcmp(seq->name, viewed) == 0) {
287			toc->viewedseq = seq;
288			*viewed = 0;
289		    }
290		    if (strcmp(seq->name, selected) == 0) {
291			toc->selectseq = seq;
292			*selected = 0;
293		    }
294		}
295	    }
296	}
297	(void) myfclose(fid);
298    }
299}
300
301
302
303/* Refigure what messages are visible. */
304
305void TURefigureWhatsVisible(Toc toc)
306{
307    MsgList mlist;
308    Msg msg, oldcurmsg;
309    int i;
310    int	w, changed, newval, msgid;
311    Sequence seq = toc->viewedseq;
312    mlist = seq->mlist;
313    oldcurmsg = toc->curmsg;
314    TocSetCurMsg(toc, (Msg)NULL);
315    w = 0;
316    changed = FALSE;
317
318    for (i = 0; i < toc->nummsgs; i++) {
319	msg = toc->msgs[i];
320	msgid = msg->msgid;
321	while (mlist && mlist->msglist[w] && mlist->msglist[w]->msgid < msgid)
322	    w++;
323	newval = (!mlist
324		  || (mlist->msglist[w] && mlist->msglist[w]->msgid == msgid));
325	if (newval != msg->visible) {
326	    changed = TRUE;
327	    msg->visible = newval;
328	}
329    }
330    if (changed) {
331	TURefigureTocPositions(toc);
332	if (oldcurmsg) {
333	    if (!oldcurmsg->visible) {
334		toc->curmsg = TocMsgAfter(toc, oldcurmsg);
335		if (toc->curmsg == NULL)
336		    toc->curmsg = TocMsgBefore(toc, oldcurmsg);
337	    } else toc->curmsg = oldcurmsg;
338	}
339	for (i=0 ; i<toc->num_scrns ; i++)
340	    TURedisplayToc(toc->scrn[i]);
341    } else TocSetCurMsg(toc, oldcurmsg);
342    for (i=0 ; i<toc->num_scrns ; i++)
343	TUResetTocLabel(toc->scrn[i]);
344}
345
346
347/* (Re)load the toc from the scanfile.  If reloading, this makes efforts to
348   keep the fates of msgs, and to keep msgs that are being edited.  Note that
349   this routine must know of all places that msg ptrs are stored; it expects
350   them to be kept only in tocs, in scrns, and in msg sequences. */
351
352#define SeemsIdentical(msg1, msg2) ((msg1)->msgid == (msg2)->msgid &&	      \
353				    ((msg1)->temporary || (msg2)->temporary ||\
354				     strcmp((msg1)->buf, (msg2)->buf) == 0))
355
356void TULoadTocFile(Toc toc)
357{
358    int maxmsgs, l, orignummsgs, i, j, origcurmsgid;
359    FILEPTR fid;
360    XawTextPosition position;
361    char *ptr;
362    Msg msg, curmsg;
363    Msg *origmsgs;
364    int bufsiz = app_resources.toc_width + 1;
365    static char *buf;
366
367    if (!buf)
368	buf = XtMalloc((Cardinal) bufsiz);
369    TocStopUpdate(toc);
370    toc->lastreaddate = LastModifyDate(toc->scanfile);
371    if (toc->curmsg) {
372	origcurmsgid = toc->curmsg->msgid;
373	TocSetCurMsg(toc, (Msg)NULL);
374    } else origcurmsgid = 0;  /* The "default" current msg; 0 means none */
375    fid = FOpenAndCheck(toc->scanfile, "r");
376    maxmsgs = orignummsgs = toc->nummsgs;
377    if (maxmsgs == 0) maxmsgs = 100;
378    toc->nummsgs = 0;
379    origmsgs = toc->msgs;
380    toc->msgs = XtMallocArray((Cardinal) maxmsgs, sizeof(Msg));
381    position = 0;
382    i = 0;
383    curmsg = NULL;
384    while ((ptr = fgets(buf, bufsiz, fid))) {
385	toc->msgs[toc->nummsgs++] = msg = XtNew(MsgRec);
386	bzero((char *) msg, sizeof(MsgRec));
387	msg->toc = toc;
388	msg->position = position;
389	msg->length = l = strlen(ptr);
390	position += l;
391	if (l == app_resources.toc_width && buf[bufsiz-2] != '\n') {
392	    buf[bufsiz-2] = '\n';
393	    msg->buf = strcpy(XtMalloc((Cardinal) ++l), ptr);
394	    msg->msgid = atoi(ptr);
395	    do
396		ptr = fgets(buf, bufsiz, fid);
397	    while (ptr && (int) strlen(ptr) == app_resources.toc_width
398		   && buf[bufsiz-2] != '\n');
399	} else {
400	    msg->buf = strcpy(XtMalloc((Cardinal) ++l), ptr);
401	    msg->msgid = atoi(ptr);
402	}
403	if (msg->msgid == origcurmsgid)
404	    curmsg = msg;
405	msg->buf[MARKPOS] = ' ';
406	msg->changed = FALSE;
407	msg->fate = Fignore;
408	msg->desttoc = NULL;
409	msg->visible = TRUE;
410	if (toc->nummsgs >= maxmsgs) {
411	    maxmsgs += 100;
412	    toc->msgs = XtReallocArray(toc->msgs, maxmsgs, sizeof(Msg));
413	}
414	while (i < orignummsgs && origmsgs[i]->msgid < msg->msgid) i++;
415	if (i < orignummsgs) {
416	    origmsgs[i]->buf[MARKPOS] = ' ';
417	    if (SeemsIdentical(origmsgs[i], msg))
418		MsgSetFate(msg, origmsgs[i]->fate, origmsgs[i]->desttoc);
419	}
420    }
421    toc->length = toc->origlength = toc->lastPos = position;
422    toc->msgs = XtReallocArray(toc->msgs, toc->nummsgs, sizeof(Msg));
423    (void) myfclose(fid);
424    if ( (toc->source == NULL) && ( toc->num_scrns > 0 ) ) {
425        Arg args[1];
426
427	XtSetArg(args[0], XtNtoc, toc);
428	toc->source = XtCreateWidget("tocSource", tocSourceWidgetClass,
429				     toc->scrn[0]->tocwidget,
430				     args, (Cardinal) 1);
431    }
432    for (i=0 ; i<numScrns ; i++) {
433	msg = scrnList[i]->msg;
434	if (msg && msg->toc == toc) {
435	    for (j=0 ; j<toc->nummsgs ; j++) {
436		if (SeemsIdentical(toc->msgs[j], msg)) {
437		    msg->position = toc->msgs[j]->position;
438		    msg->visible = TRUE;
439		    ptr = toc->msgs[j]->buf;
440		    l = toc->msgs[j]->length;
441		    *(toc->msgs[j]) = *msg;
442		    toc->msgs[j]->buf = ptr;
443		    toc->msgs[j]->length = l;
444		    scrnList[i]->msg = toc->msgs[j];
445		    break;
446		}
447	    }
448	    if (j >= toc->nummsgs) {
449		msg->temporary = FALSE;	/* Don't try to auto-delete msg. */
450		MsgSetScrnForce(msg, (Scrn) NULL);
451	    }
452	}
453    }
454    for (i=0 ; i<orignummsgs ; i++)
455	MsgFree(origmsgs[i]);
456    XtFree((char *)origmsgs);
457    TocSetCurMsg(toc, curmsg);
458    TULoadSeqLists(toc);
459    TocStartUpdate(toc);
460}
461
462
463void TUSaveTocFile(Toc toc)
464{
465    Msg msg;
466    int fid;
467    int i;
468    off_t position;
469    char c;
470    if (toc->stopupdate) {
471	toc->needscachesave = TRUE;
472	return;
473    }
474    fid = -1;
475    position = 0;
476    for (i = 0; i < toc->nummsgs; i++) {
477	msg = toc->msgs[i];
478	if (fid < 0 && msg->changed) {
479	    fid = myopen(toc->scanfile, O_RDWR, 0666);
480	    (void) lseek(fid, position, SEEK_SET);
481	}
482	if (fid >= 0) {
483	    c = msg->buf[MARKPOS];
484	    msg->buf[MARKPOS] = ' ';
485	    (void) write(fid, msg->buf, msg->length);
486	    msg->buf[MARKPOS] = c;
487	}
488	position += msg->length;
489    }
490    if (fid < 0 && toc->length != toc->origlength)
491	fid = myopen(toc->scanfile, O_RDWR, 0666);
492    if (fid >= 0) {
493	(void) ftruncate(fid, toc->length);
494	myclose(fid);
495	toc->origlength = toc->length;
496    }
497    toc->needscachesave = FALSE;
498    toc->lastreaddate = LastModifyDate(toc->scanfile);
499}
500
501
502static Boolean UpdateScanFile(
503  XtPointer client_data)	/* Toc */
504{
505    Toc toc = (Toc)client_data;
506    int i;
507
508    if (app_resources.block_events_on_busy) ShowBusyCursor();
509
510    TUScanFileForToc(toc);
511    TULoadTocFile(toc);
512
513    for (i=0 ; i<toc->num_scrns ; i++)
514	TURedisplayToc(toc->scrn[i]);
515
516    if (app_resources.block_events_on_busy) UnshowBusyCursor();
517
518    return True;
519}
520
521
522void TUEnsureScanIsValidAndOpen(Toc toc, Boolean delay)
523{
524    if (toc) {
525	TUGetFullFolderInfo(toc);
526	if (TUScanFileOutOfDate(toc)) {
527	    if (!delay)
528		UpdateScanFile((XtPointer)toc);
529	    else {
530		/* this is a hack to get the screen mapped before
531		 * spawning the subprocess (and blocking).
532		 * Need to make sure the scanfile exists at this point.
533		 */
534		int fid = myopen(toc->scanfile, O_RDWR|O_CREAT, 0666);
535		myclose(fid);
536		XtAppAddWorkProc(XtWidgetToApplicationContext(toplevel),
537				 UpdateScanFile,
538				 (XtPointer)toc);
539	    }
540	}
541	if (toc->source == NULL)
542	    TULoadTocFile(toc);
543    }
544}
545
546
547
548/* Refigure all the positions, based on which lines are visible. */
549
550void TURefigureTocPositions(Toc toc)
551{
552    int i;
553    Msg msg;
554    XawTextPosition position, length;
555    position = length = 0;
556    for (i=0; i<toc->nummsgs ; i++) {
557	msg = toc->msgs[i];
558	msg->position = position;
559	if (msg->visible) position += msg->length;
560	length += msg->length;
561    }
562    toc->lastPos = position;
563    toc->length = length;
564}
565
566
567
568/* Make sure we've loaded ALL the folder info for this toc, including its
569   path and sequence lists. */
570
571void TUGetFullFolderInfo(Toc toc)
572{
573    if (! toc->scanfile) {
574	if (! toc->path) {
575	    /* usually preset by TocFolderExists */
576	    XtAsprintf(&toc->path, "%s/%s", app_resources.mail_path,
577                       toc->foldername);
578	}
579	XtAsprintf(&toc->scanfile, "%s/.xmhcache", toc->path);
580	toc->lastreaddate = LastModifyDate(toc->scanfile);
581	if (TUScanFileOutOfDate(toc))
582	    toc->validity = invalid;
583	else {
584	    toc->validity = valid;
585	    TULoadTocFile(toc);
586	}
587    }
588}
589
590/* Append a message to the end of the toc.  It has the given scan line.  This
591   routine will figure out the message number, and change the scan line
592   accordingly. */
593
594Msg TUAppendToc(Toc toc, const char *ptr)
595{
596    Msg msg;
597    int msgid;
598
599    TUGetFullFolderInfo(toc);
600    if (toc->validity != valid)
601	return NULL;
602
603    if (toc->nummsgs > 0)
604	msgid = toc->msgs[toc->nummsgs - 1]->msgid + 1;
605    else
606	msgid = 1;
607    (toc->nummsgs)++;
608    toc->msgs = XtReallocArray(toc->msgs, toc->nummsgs, sizeof(Msg));
609    toc->msgs[toc->nummsgs - 1] = msg = XtNew(MsgRec);
610    bzero((char *) msg, (int) sizeof(MsgRec));
611    msg->toc = toc;
612    msg->buf = XtNewString(ptr);
613    if (msgid >= 10000)
614	msgid %= 10000;
615    snprintf(msg->buf, strlen(msg->buf) + 1, "%4d", msgid);
616    msg->buf[MARKPOS] = ' ';
617    msg->msgid = msgid;
618    msg->position = toc->lastPos;
619    msg->length = strlen(ptr);
620    msg->changed = TRUE;
621    msg->fate = Fignore;
622    msg->desttoc = NULL;
623    if (toc->viewedseq == toc->seqlist[0]) {
624	msg->visible = TRUE;
625	toc->lastPos += msg->length;
626    }
627    else
628	msg->visible = FALSE;
629    toc->length += msg->length;
630    if ( (msg->visible) && (toc->source != NULL) )
631	TSourceInvalid(toc, msg->position, msg->length);
632    TUSaveTocFile(toc);
633    return msg;
634}
635