1: /***************************************************************************
   2:  * This program is Copyright (C) 1986, 1987, 1988 by Jonathan Payne.  JOVE *
   3:  * is provided to you without charge, and with no warranty.  You may give  *
   4:  * away copies of JOVE, including sources, provided that this notice is    *
   5:  * included in all the files.                                              *
   6:  ***************************************************************************/
   7: 
   8: #include "jove.h"
   9: #include "ctype.h"
  10: #include "termcap.h"
  11: 
  12: 
  13: #ifdef MAC
  14: #	include "mac.h"
  15: #else
  16: #	include <varargs.h>
  17: #	include <sys/stat.h>
  18: #endif
  19: 
  20: #include <signal.h>
  21: 
  22: #ifdef MAC
  23: #	undef private
  24: #	define private
  25: #endif
  26: 
  27: #ifdef  LINT_ARGS
  28: private void
  29: #ifdef ID_CHAR
  30:     DeTab(int, char *, char *, int, int),
  31:     DelChar(int, int, int),
  32:     InsChar(int, int, int, char *),
  33: #endif
  34:     DoIDline(int),
  35:     do_cl_eol(int),
  36:     ModeLine(Window *),
  37:     mode_app(char *),
  38:     GotoDot(void),
  39:     UpdLine(int),
  40:     UpdWindow(Window *, int);
  41: 
  42: private int
  43:     AddLines(int, int),
  44:     DelLines(int, int),
  45:     UntilEqual(int);
  46: #else
  47: private void
  48: #ifdef ID_CHAR
  49:     DeTab(),
  50:     DelChar(),
  51:     InsChar(),
  52: #endif
  53:     DoIDline(),
  54:     do_cl_eol(),
  55:     GotoDot(),
  56:     ModeLine(),
  57:     mode_app(),
  58:     UpdLine(),
  59:     UpdWindow();
  60: private int
  61:     AddLines(),
  62:     DelLines(),
  63:     UntilEqual();
  64: #endif	/* LINT_ARGS */
  65: 
  66: #ifdef MAC
  67: #	undef private
  68: #	define private static
  69: #endif
  70: 
  71: int DisabledRedisplay = NO;
  72: 
  73: /* Kludge windows gets called by the routines that delete lines from the
  74:    buffer.  If the w->w_line or w->w_top are deleted and this procedure
  75:    is not called, the redisplay routine will barf. */
  76: 
  77: void
  78: ChkWindows(line1, line2)
  79: Line    *line1;
  80: register Line   *line2;
  81: {
  82:     register Window *w = fwind;
  83:     register Line   *lp;
  84: 
  85:     do {
  86:         for (lp = line1->l_next; lp != line2->l_next; lp = lp->l_next) {
  87:             if (lp == w->w_top)
  88:                 w->w_flags |= W_TOPGONE;
  89:             if (lp == w->w_line)
  90:                 w->w_flags |= W_CURGONE;
  91:         }
  92:         w = w->w_next;
  93:     } while (w != fwind);
  94: }
  95: 
  96: extern int  RingBell;
  97: 
  98: void
  99: redisplay()
 100: {
 101:     extern int  AbortCnt;
 102:     register Window *w = fwind;
 103:     int lineno,
 104:         done_ID = NO,
 105:         i;
 106:     register struct scrimage    *des_p,
 107:                     *phys_p;
 108: 
 109:     if (DisabledRedisplay == YES)
 110:         return;
 111:     curwind->w_line = curwind->w_bufp->b_dot;
 112:     curwind->w_char = curwind->w_bufp->b_char;
 113: #ifdef MAC
 114:     InputPending = 0;
 115: #else
 116:     if (InputPending = charp()) /* calls CheckEvent, which could */
 117:         return; /* result in a call to rediplay(). We don't want that. */
 118: #endif
 119: #ifdef JOB_CONTROL
 120:     if (UpdFreq)
 121:         sighold(SIGALRM);
 122: #endif
 123:     if (RingBell) {
 124:         dobell(1);
 125:         RingBell = 0;
 126:     }
 127:     AbortCnt = BufSize;     /* initialize this now */
 128:     if (UpdMesg)
 129:         DrawMesg(YES);
 130: 
 131:     for (lineno = 0, w = fwind; lineno < ILI; w = w->w_next) {
 132:         UpdWindow(w, lineno);
 133:         lineno += w->w_height;
 134:     }
 135: 
 136:     UpdModLine = 0; /* Now that we've called update window, we can
 137: 			   assume that the modeline will be updated.  But
 138: 			   if while redrawing the modeline the user types
 139: 			   a character, ModeLine() is free to set this on
 140: 			   again so that the modeline will be fully drawn
 141: 			   at the next redisplay. */
 142: 
 143:     des_p = DesiredScreen;
 144:     phys_p = PhysScreen;
 145:     for (i = 0; i < ILI; i++, des_p++, phys_p++) {
 146:         if (!done_ID && (des_p->s_id != phys_p->s_id)) {
 147:             DoIDline(i);
 148:             done_ID = YES;
 149:         }
 150:         if ((des_p->s_flags & (DIRTY | L_MOD)) ||
 151:             (des_p->s_id != phys_p->s_id) ||
 152:             (des_p->s_vln != phys_p->s_vln) ||
 153:             (des_p->s_offset != phys_p->s_offset))
 154:             UpdLine(i);
 155:         if (InputPending)
 156:             goto ret;
 157:     }
 158: 
 159: 
 160:     if (Asking) {
 161:         Placur(LI - 1, min(CO - 2, calc_pos(mesgbuf, Asking)));
 162:             /* Nice kludge */
 163:         flusho();
 164:     } else
 165:         GotoDot();
 166: ret:
 167: #ifdef JOB_CONTROL
 168:     if (UpdFreq)
 169:         sigrelse(SIGALRM);
 170: #else
 171:     ;   /* yuck */
 172: #endif
 173: #ifdef MAC
 174:     if(Windchange) docontrols();
 175: #endif /* MAC */
 176: }
 177: 
 178: #ifndef IBMPC
 179: private void
 180: dobell(n)
 181: {
 182:     while (--n >= 0) {
 183: #ifndef MAC
 184:         if (VisBell && VB)
 185:             putstr(VB);
 186:         else
 187:             putpad(BL, 1);
 188: #else
 189:         SysBeep(5);
 190: #endif
 191:     }
 192:     flusho();
 193: }
 194: #endif /* IBMPC */
 195: 
 196: /* find_pos() returns the position on the line, that C_CHAR represents
 197:    in LINE */
 198: 
 199: int
 200: find_pos(line, c_char)
 201: Line    *line;
 202: {
 203:     return calc_pos(lcontents(line), c_char);
 204: }
 205: 
 206: int
 207: calc_pos(lp, c_char)
 208: register char   *lp;
 209: register int    c_char;
 210: {
 211:     register int    pos = 0;
 212:     register int    c;
 213: 
 214: 
 215:     while ((--c_char >= 0) && ((c = *lp++) & CHARMASK) != 0) {
 216:         if (c == '\t')
 217:             pos += (tabstop - (pos % tabstop));
 218:         else if (isctrl(c))
 219:             pos += 2;
 220:         else
 221:             pos += 1;
 222:     }
 223:     return pos;
 224: }
 225: 
 226: int UpdModLine = 0,
 227:     UpdMesg = 0,
 228:     CanScroll = 0;
 229: 
 230: private void
 231: DoIDline(start)
 232: {
 233:     register struct scrimage    *des_p = &DesiredScreen[start];
 234:     struct scrimage *phys_p = &PhysScreen[start];
 235:     register int    i,
 236:             j;
 237: 
 238:     /* Some changes have been made.  Try for insert or delete lines.
 239: 	   If either case has happened, Addlines and/or DelLines will do
 240: 	   necessary scrolling, also CONVERTING PhysScreen to account for the
 241: 	   physical changes.  The comparison continues from where the
 242: 	   insertion/deletion takes place; this doesn't happen very often,
 243: 	   usually it happens with more than one window with the same
 244: 	   buffer. */
 245: 
 246:     if (!CanScroll)
 247:         return;     /* We should never have been called! */
 248: 
 249:     for (i = start; i < ILI; i++, des_p++, phys_p++)
 250:         if (des_p->s_id != phys_p->s_id)
 251:             break;
 252: 
 253:     for (; i < ILI; i++) {
 254:         for (j = i + 1; j < ILI; j++) {
 255:             des_p = &DesiredScreen[j];
 256:             phys_p = &PhysScreen[j];
 257:             if (des_p->s_id != 0 && des_p->s_id == phys_p->s_id)
 258:                 break;
 259:             if (des_p->s_id == PhysScreen[i].s_id) {
 260:                 if (des_p->s_id == 0)
 261:                     continue;
 262:                 if (AddLines(i, j - i)) {
 263:                     DoIDline(j);
 264:                     return;
 265:                 }
 266:                 break;
 267:             }
 268:             if ((des_p = &DesiredScreen[i])->s_id == phys_p->s_id) {
 269:                 if (des_p->s_id == 0)
 270:                     continue;
 271:                 if (DelLines(i, j - i)) {
 272:                     DoIDline(i);
 273:                     return;
 274:                 }
 275:                 break;
 276:             }
 277:         }
 278:     }
 279: }
 280: 
 281: /* Make DesiredScreen reflect what the screen should look like when we are done
 282:    with the redisplay.  This deals with horizontal scrolling.  Also makes
 283:    sure the current line of the Window is in the window. */
 284: 
 285: int ScrollAll = NO;
 286: 
 287: private void
 288: UpdWindow(w, start)
 289: register Window *w;
 290: {
 291:     Line    *lp;
 292:     int i,
 293:         upper,      /* top of window */
 294:         lower,      /* bottom of window */
 295:         strt_col,   /* starting print column of current line */
 296:         ntries = 0; /* # of tries at updating window */
 297:     register struct scrimage    *des_p,
 298:                     *phys_p;
 299:     Buffer  *bp = w->w_bufp;
 300: 
 301: retry:
 302:     if (w->w_flags & W_CURGONE) {
 303:         w->w_line = bp->b_dot;
 304:         w->w_char = bp->b_char;
 305:     }
 306:     if (w->w_flags & W_TOPGONE)
 307:         CentWind(w);    /* reset topline of screen */
 308:     w->w_flags &= ~(W_CURGONE | W_TOPGONE);
 309: 
 310:     /* make sure that the current line is in the window */
 311:     upper = start;
 312:     lower = upper + w->w_height - 1;    /* don't include modeline */
 313:     for (i = upper, lp = w->w_top; i < lower && lp != 0; lp = lp->l_next, i++)
 314:         if (lp == w->w_line)
 315:             break;
 316:     if (i == lower || lp == 0) {
 317:         ntries += 1;
 318:         if (ntries == 1) {
 319:             CalcWind(w);
 320:             goto retry;
 321:         } else if (ntries == 2) {
 322:             w->w_top = w->w_line = w->w_bufp->b_first;
 323:             printf("\rERROR in redisplay: I got hopelessly lost!");
 324:             dobell(2);
 325:             goto retry;
 326:         } else if (ntries == 3) {
 327:             printf("\n\rOops, still lost, quitting ...\r\n");
 328:             finish(1);
 329:         }
 330:     }
 331: 
 332:     /* first do some calculations for the current line */
 333:     {
 334:         int diff = (w->w_flags & W_NUMLINES) ? 8 : 0,
 335:             end_col;
 336: 
 337:         strt_col = (ScrollAll == YES) ? w->w_LRscroll :
 338:                PhysScreen[i].s_offset;
 339:         end_col = strt_col + (CO - 2) - diff;
 340:         /* Right now we are displaying from strt_col to
 341: 		   end_col of the buffer line.  These are PRINT
 342: 		   columns, not actual characters. */
 343:         w->w_dotcol = find_pos(w->w_line, w->w_char);
 344:         /* if the new dotcol is out of range, reselect
 345: 		   a horizontal window */
 346:         if ((PhysScreen[i].s_offset == -1) ||
 347:             (w->w_dotcol < strt_col) ||
 348:             (w->w_dotcol >= end_col)) {
 349:             if (w->w_dotcol < ((CO - 2) - diff))
 350:                 strt_col = 0;
 351:             else
 352:                 strt_col = w->w_dotcol - (CO / 2);
 353:             if (ScrollAll == YES) {
 354:                 if (w->w_LRscroll != strt_col)
 355:                     UpdModLine = YES;
 356:                 w->w_LRscroll = strt_col;
 357:             }
 358:         }
 359:         w->w_dotline = i;
 360:         w->w_dotcol += diff;
 361:     }
 362: 
 363:     des_p = &DesiredScreen[upper];
 364:     phys_p = &PhysScreen[upper];
 365:     for (i = upper, lp = w->w_top; lp != 0 && i < lower; i++, des_p++, phys_p++, lp = lp->l_next) {
 366:         des_p->s_window = w;
 367:         des_p->s_lp = lp;
 368:         des_p->s_id = lp->l_dline & ~DIRTY;
 369:         des_p->s_flags = isdirty(lp) ? L_MOD : 0;
 370:         if (w->w_flags & W_NUMLINES)
 371:             des_p->s_vln = w->w_topnum + (i - upper);
 372:         else
 373:             des_p->s_vln = 0;
 374: 
 375:         if (lp == w->w_line)
 376:             des_p->s_offset = strt_col;
 377:         else
 378:             des_p->s_offset = w->w_LRscroll;
 379:     }
 380: 
 381:     /* Is structure assignment faster than copy each field separately? */
 382:     if (i < lower) {
 383:         static struct scrimage  dirty_plate = { 0, DIRTY, 0, 0, 0, 0 },
 384:                     clean_plate = { 0, 0, 0, 0, 0, 0 };
 385: 
 386:         for (; i < lower; i++, des_p++, phys_p++)
 387:             if (phys_p->s_id != 0)
 388:                 *des_p = dirty_plate;
 389:             else
 390:                 *des_p = clean_plate;
 391:     }
 392: 
 393:     des_p->s_window = w;
 394:     des_p->s_flags = 0;
 395:     if (((des_p->s_id = (int) w->w_bufp) != phys_p->s_id) || UpdModLine)
 396:         des_p->s_flags = MODELINE | DIRTY;
 397: #ifdef MAC
 398:     if(UpdModLine) Modechange = 1;
 399:     if(w == curwind && w->w_control) SetScrollBar(w->w_control);
 400: #endif
 401: }
 402: 
 403: /* Write whatever is in mesgbuf (maybe we are Asking, or just printed
 404:    a message).  Turns off the UpdateMesg line flag. */
 405: 
 406: void
 407: DrawMesg(abortable)
 408: {
 409: #ifndef MAC     /* same reason as in redisplay() */
 410:     if (charp())
 411:         return;
 412: #endif
 413:     i_set(ILI, 0);
 414:     if (swrite(mesgbuf, NO, abortable)) {
 415:         cl_eol();
 416:         UpdMesg = 0;
 417:     }
 418:     flusho();
 419: }
 420: 
 421: /* Goto the current position in the current window.  Presumably redisplay()
 422:    has already been called, and curwind->{w_dotline,w_dotcol} have been set
 423:    correctly. */
 424: 
 425: private void
 426: GotoDot()
 427: {
 428:     if (InputPending)
 429:         return;
 430:     Placur(curwind->w_dotline, curwind->w_dotcol -
 431:                 PhysScreen[curwind->w_dotline].s_offset);
 432:     flusho();
 433: }
 434: 
 435: private int
 436: UntilEqual(start)
 437: register int    start;
 438: {
 439:     register struct scrimage    *des_p = &DesiredScreen[start],
 440:                     *phys_p = &PhysScreen[start];
 441: 
 442:     while ((start < ILI) && (des_p->s_id != phys_p->s_id)) {
 443:         des_p += 1;
 444:         phys_p += 1;
 445:         start += 1;
 446:     }
 447: 
 448:     return start;
 449: }
 450: 
 451: /* Calls the routine to do the physical changes, and changes PhysScreen to
 452:    reflect those changes. */
 453: 
 454: private int
 455: AddLines(at, num)
 456: register int    at,
 457:         num;
 458: {
 459:     register int    i;
 460:     int bottom = UntilEqual(at + num);
 461: 
 462:     if (num == 0 || num >= ((bottom - 1) - at))
 463:         return NO;              /* we did nothing */
 464:     v_ins_line(num, at, bottom - 1);
 465: 
 466:     /* Now change PhysScreen to account for the physical change. */
 467: 
 468:     for (i = bottom - 1; i - num >= at; i--)
 469:         PhysScreen[i] = PhysScreen[i - num];
 470:     for (i = 0; i < num; i++)
 471:         PhysScreen[at + i].s_id = 0;
 472:     return YES;                 /* we did something */
 473: }
 474: 
 475: private int
 476: DelLines(at, num)
 477: register int    at,
 478:         num;
 479: {
 480:     register int    i;
 481:     int bottom = UntilEqual(at + num);
 482: 
 483:     if (num == 0 || num >= ((bottom - 1) - at))
 484:         return NO;
 485:     v_del_line(num, at, bottom - 1);
 486: 
 487:     for (i = at; num + i < bottom; i++)
 488:         PhysScreen[i] = PhysScreen[num + i];
 489:     for (i = bottom - num; i < bottom; i++)
 490:         PhysScreen[i].s_id = 0;
 491:     return YES;
 492: }
 493: 
 494: /* Update line linenum in window w.  Only set PhysScreen to DesiredScreen
 495:    if the swrite or cl_eol works, that is nothing is interupted by
 496:    characters typed. */
 497: 
 498: private void
 499: UpdLine(linenum)
 500: register int    linenum;
 501: {
 502:     register struct scrimage    *des_p = &DesiredScreen[linenum];
 503:     register Window *w = des_p->s_window;
 504: 
 505:     i_set(linenum, 0);
 506:     if (des_p->s_flags & MODELINE)
 507:         ModeLine(w);
 508:     else if (des_p->s_id) {
 509:         des_p->s_lp->l_dline &= ~DIRTY;
 510:         des_p->s_flags &= ~(DIRTY | L_MOD);
 511: #ifdef ID_CHAR
 512:         if (!UseIC && (w->w_flags & W_NUMLINES))
 513: #else
 514:         if (w->w_flags & W_NUMLINES)
 515: #endif
 516:             (void) swrite(sprint("%6d  ", des_p->s_vln), NO, YES);
 517: 
 518: #ifdef ID_CHAR
 519:         if (UseIC) {
 520:             char    outbuf[MAXCOLS],
 521:                 *lptr;
 522:             int fromcol = (w->w_flags & W_NUMLINES) ? 8 : 0;
 523: 
 524:             if (w->w_flags & W_NUMLINES)
 525:                 sprintf(outbuf, "%6d  ", des_p->s_vln);
 526:             lptr = lcontents(des_p->s_lp);
 527:             DeTab(des_p->s_offset, lptr, outbuf + fromcol,
 528:                 (sizeof outbuf) - 1 - fromcol,
 529:                 des_p->s_window->w_flags & W_VISSPACE);
 530:             if (IDchar(outbuf, linenum, 0))
 531:                 PhysScreen[linenum] = *des_p;
 532:             else if (i_set(linenum, 0), swrite(outbuf, NO, YES))
 533:                 do_cl_eol(linenum);
 534:             else
 535:                 PhysScreen[linenum].s_id = -1;
 536:         } else
 537: #endif /* ID_CHAR */
 538:             if (BufSwrite(linenum))
 539:             do_cl_eol(linenum);
 540:         else
 541:             PhysScreen[linenum].s_id = -1;
 542:     } else if (PhysScreen[linenum].s_id)    /* not the same ... make sure */
 543:         do_cl_eol(linenum);
 544: }
 545: 
 546: private void
 547: do_cl_eol(linenum)
 548: register int    linenum;
 549: {
 550:     cl_eol();
 551:     PhysScreen[linenum] = DesiredScreen[linenum];
 552: }
 553: 
 554: #ifdef ID_CHAR
 555: 
 556: /* From here to the end of the file is code that tries to utilize the
 557:    insert/delete character feature on some terminals.  It is very confusing
 558:    and not so well written code, AND there is a lot of it.  You may want
 559:    to use the space for something else. */
 560: 
 561: extern struct screenline    *Screen;
 562: int IN_INSmode = 0;
 563: 
 564: int UseIC;
 565: 
 566: int DClen,
 567:     MDClen,
 568:     IClen,
 569:     MIClen,
 570:     IMlen,
 571:     CElen;
 572: 
 573: void
 574: disp_opt_init()
 575: {
 576:     DClen = DC ? strlen(DC) : 0;
 577:     MDClen = M_DC ? strlen(M_DC) : 9999;
 578:     IClen = IC ? strlen(IC) : 0;
 579:     MIClen = M_IC ? strlen(M_IC) : 9999;
 580:     IMlen = IM ? strlen(IM) : 0;
 581:     CElen = CE ? strlen(CE) : 0;
 582: 
 583:     UseIC = (IC || IM || M_IC);
 584: }
 585: 
 586: void
 587: INSmode(on)
 588: {
 589:     if (on && !IN_INSmode) {
 590:         putpad(IM, 1);
 591:         IN_INSmode = YES;
 592:     } else if (!on && IN_INSmode) {
 593:         putpad(EI, 1);
 594:         IN_INSmode = NO;
 595:     }
 596: }
 597: 
 598: private void
 599: DeTab(s_offset, buf, outbuf, limit, visspace)
 600: register char   *buf;
 601: char    *outbuf;
 602: {
 603:     register char   *phys_p = outbuf,
 604:             c;
 605:     register int    pos = 0;
 606:     char        *limitp = &outbuf[limit];
 607: 
 608: #define OkayOut(ch) if ((pos++ >= s_offset) && (phys_p < limitp))\
 609:                 *phys_p++ = ch;\
 610:             else
 611: 
 612:     while (c = *buf++) {
 613:         if (c == '\t') {
 614:             int nchars = (tabstop - (pos % tabstop));
 615: 
 616:             if (visspace) {
 617:                 OkayOut('>');
 618:                 nchars -= 1;
 619:             }
 620:             while (--nchars >= 0)
 621:                 OkayOut(' ');
 622: 
 623:         } else if (isctrl(c)) {
 624:             OkayOut('^');
 625:             OkayOut(c == 0177 ? '?' : c + '@');
 626:         } else {
 627:             if (visspace && c == ' ')
 628:                 c = '_';
 629:             OkayOut(c);
 630:         }
 631:         if (pos - s_offset >= CO) {
 632:             phys_p = &outbuf[CO - 1];
 633:             *phys_p++ = '!';
 634:             break;
 635:         }
 636:     }
 637:     *phys_p = 0;
 638: }
 639: 
 640: /* ID character routines full of special cases and other fun stuff like that.
 641:    It actually works though ...
 642: 
 643:   	Returns Non-Zero if you are finished (no differences left). */
 644: 
 645: private int
 646: IDchar(new, lineno, col)
 647: register char   *new;
 648: {
 649:     register int    i;
 650:     int j,
 651:         oldlen,
 652:         NumSaved;
 653:     register struct screenline  *sline = &Screen[lineno];
 654: 
 655:     oldlen = sline->s_length - sline->s_line;
 656: 
 657:     for (i = col; i < oldlen && new[i] != 0; i++)
 658:         if (sline->s_line[i] != new[i])
 659:             break;
 660:     if (new[i] == 0 || i == oldlen)
 661:         return (new[i] == 0 && i == oldlen);
 662: 
 663:     for (j = i + 1; j < oldlen && new[j]; j++) {
 664:         if (new[j] == sline->s_line[i]) {
 665:             NumSaved = IDcomp(new + j, sline->s_line + i,
 666:                     strlen(new)) + NumSimilar(new + i,
 667:                         sline->s_line + i, j - i);
 668:             if (OkayInsert(NumSaved, j - i)) {
 669:                 InsChar(lineno, i, j - i, new);
 670:                 return(IDchar(new, lineno, j));
 671:             }
 672:         }
 673:     }
 674: 
 675:     for (j = i + 1; j < oldlen && new[i]; j++) {
 676:         if (new[i] == sline->s_line[j]) {
 677:             NumSaved = IDcomp(new + i, sline->s_line + j,
 678:                     oldlen - j);
 679:             if (OkayDelete(NumSaved, j - i, new[oldlen] == 0)) {
 680:                 DelChar(lineno, i, j - i);
 681:                 return(IDchar(new, lineno, j));
 682:             }
 683:         }
 684:     }
 685:     return 0;
 686: }
 687: 
 688: private int
 689: NumSimilar(s, t, n)
 690: register char   *s,
 691:         *t;
 692: {
 693:     register int    num = 0;
 694: 
 695:     while (n--)
 696:         if (*s++ == *t++)
 697:             num += 1;
 698:     return num;
 699: }
 700: 
 701: private int
 702: IDcomp(s, t, len)
 703: register char   *s,
 704:         *t;
 705: {
 706:     register int    i;
 707:     int num = 0,
 708:         nonspace = 0;
 709:     char    c;
 710: 
 711:     for (i = 0; i < len; i++) {
 712:         if ((c = *s++) != *t++)
 713:             break;
 714:         if (c != ' ')
 715:             nonspace++;
 716:         if (nonspace)
 717:             num += 1;
 718:     }
 719: 
 720:     return num;
 721: }
 722: 
 723: private int
 724: OkayDelete(Saved, num, samelength)
 725: {
 726:     /* If the old and the new are the same length, then we don't
 727: 	 * have to clear to end of line.  We take that into consideration.
 728: 	 */
 729:     return ((Saved + (!samelength ? CElen : 0))
 730:         > min(MDClen, DClen * num));
 731: }
 732: 
 733: private int
 734: OkayInsert(Saved, num)
 735: {
 736:     register int    n = 0;
 737: 
 738:     if (IC)     /* Per character prefixes */
 739:         n = min(num * IClen, MIClen);
 740: 
 741:     if (IM && !IN_INSmode) {
 742:         /* Good terminal.  Fewer characters in this case */
 743:         n += IMlen;
 744:     }
 745: 
 746:     n += num;   /* The characters themselves */
 747: 
 748:     return Saved > n;
 749: }
 750: 
 751: extern int  CapCol;
 752: extern char *cursend;
 753: extern struct screenline    *Curline;
 754: 
 755: private void
 756: DelChar(lineno, col, num)
 757: {
 758:     register char   *from,
 759:             *to;
 760:     register int    i;
 761:     struct screenline *sp = (&Screen[lineno]);
 762: 
 763:     Placur(lineno, col);
 764:     if (M_DC && num > 1) {
 765:         char    minibuf[16];
 766: 
 767:         sprintf(minibuf, M_DC, num);
 768:         putpad(minibuf, num);
 769:     } else {
 770:         for (i = num; --i >= 0; )
 771:             putpad(DC, 1);
 772:     }
 773: 
 774:     to = sp->s_line + col;
 775:     from = to + num;
 776: 
 777:     byte_copy(from, to, sp->s_length - from + 1);
 778:     clrline(sp->s_length - num, sp->s_length);
 779:     sp->s_length -= num;
 780: }
 781: 
 782: private void
 783: InsChar(lineno, col, num, new)
 784: char    *new;
 785: {
 786:     register char   *sp1,
 787:             *sp2,   /* To push over the array. */
 788:             *sp3;   /* Last character to push over. */
 789:     int i;
 790: 
 791:     i_set(lineno, 0);
 792:     sp2 = Curline->s_length + num;
 793: 
 794:     if (sp2 >= cursend) {
 795:         i_set(lineno, CO - num - 1);
 796:         cl_eol();
 797:         sp2 = cursend - 1;
 798:     }
 799:     Curline->s_length = sp2;
 800:     sp1 = sp2 - num;
 801:     sp3 = Curline->s_line + col;
 802: 
 803:     while (sp1 >= sp3)
 804:         *sp2-- = *sp1--;
 805: 
 806:     new += col;
 807:     byte_copy(new, sp3, num);
 808:     /* The internal screen is correct, and now we have to do
 809: 	   the physical stuff. */
 810: 
 811:     Placur(lineno, col);
 812:     if (IM) {
 813:         if (!IN_INSmode)
 814:             INSmode(1);
 815:     } else if (M_IC && num > 1) {
 816:         char    minibuf[16];
 817: 
 818:         sprintf(minibuf, M_IC, num);
 819:         putpad(minibuf, num);
 820:     } else if (IC) {
 821:         for (i = 0; i < num; i++)
 822:             putpad(IC, 1);
 823:     }
 824:     for (i = 0; i < num; i++) {
 825:         putchar(new[i]);
 826:         if (IN_INSmode)
 827:             putpad(IP, 1);
 828:     }
 829:     CapCol += num;
 830: }
 831: 
 832: #endif /* ID_CHAR */
 833: 
 834: #ifdef UNIX     /* obviously ... no mail today if not Unix*/
 835: 
 836: /* chkmail() returns nonzero if there is new mail since the
 837:    last time we checked. */
 838: 
 839: char    Mailbox[FILESIZE];  /* initialized in main */
 840: int MailInt = 60;   /* check no more often than 60 seconds */
 841: #ifdef BIFF
 842: int BiffChk = NO;   /* whether or not to turn off biff while in JOVE */
 843: #endif
 844: 
 845: int
 846: chkmail(force)
 847: {
 848:     time_t  now;
 849:     static time_t   last_chk = 0;
 850:     static int  value = FALSE;
 851:     static off_t    last_size = 0;
 852:     struct stat stbuf;
 853:     int last_val;
 854:     static time_t   last_time = 0;
 855: 
 856:     if (MailInt == 0)
 857:         return FALSE;
 858:     time(&now);
 859:     if ((now < last_chk + MailInt) && !force)
 860:         return value;
 861:     last_chk = now;
 862:     if (force)
 863:         last_time = now;
 864:     if (stat(Mailbox, &stbuf) < 0) {
 865:         value = FALSE;
 866:         return FALSE;
 867:     }
 868:     last_val = value;
 869:     value = ((stbuf.st_mtime > last_time) &&
 870:          (stbuf.st_size > 0) &&
 871:          (stbuf.st_size >= last_size) &&
 872:          (stbuf.st_mtime + 5 > stbuf.st_atime));
 873:     if (value == TRUE &&
 874:               ((value != last_val) || (stbuf.st_size != last_size)))
 875:         dobell(3);
 876:     if (stbuf.st_size < last_size)
 877:         last_time = now;
 878:     last_size = stbuf.st_size;
 879:     return value;
 880: }
 881: 
 882: #endif /* UNIX */
 883: 
 884: /* Print the mode line. */
 885: 
 886: private char    *mode_p,
 887:         *mend_p;
 888: int BriteMode = 1;      /* modeline should standout */
 889: 
 890: private void
 891: mode_app(str)
 892: register char   *str;
 893: {
 894:     if (mode_p >= mend_p)
 895:         return;
 896:     while ((mode_p < mend_p) && (*mode_p++ = *str++))
 897:         ;
 898:     mode_p -= 1;    /* back over the null */
 899: }
 900: 
 901: char    ModeFmt[120] = "%3c %w %[%sJOVE (%M)   Buffer: %b  \"%f\" %]%s%m*- %((%t)%s%)%e";
 902: 
 903: private void
 904: ModeLine(w)
 905: register Window *w;
 906: {
 907:     extern int  i_line;
 908:     extern char *pwd();
 909:     int n,
 910:         ign_some = NO;
 911:     char    line[MAXCOLS],
 912:         *fmt = ModeFmt,
 913:         fillc,
 914:         c;
 915:     register Buffer *thisbuf = w->w_bufp;
 916:     register Buffer *bp;
 917: 
 918:     mode_p = line;
 919:     mend_p = &line[(sizeof line) - 1];
 920: 
 921: #if defined(UNIX) || (defined (MSDOS) && !defined(IBMPC))
 922:     if (BriteMode != 0 && SO == 0)
 923:         BriteMode = 0;
 924:     fillc = BriteMode ? ' ' : '-';
 925: #endif /* UNIX */
 926: #ifdef IBMPC        /* very subtle - don't mess up attributes too much */
 927:     fillc = '-'; /*BriteMode ? ' ' : '-';*/
 928: #endif /* IBMPC */
 929: #ifdef MAC
 930:     fillc = '_';    /* looks better on a Mac */
 931: #endif /* MAC */
 932: 
 933:     while (c = *fmt++) {
 934:         if (c != '%') {
 935:             if (c == '\\')
 936:                 if ((c = *fmt++) == '\0')
 937:                     break;
 938:             if (!ign_some)
 939:                 *mode_p++ = c;
 940:             continue;
 941:         }
 942:         if ((c = *fmt++) == '\0')   /* char after the '%' */
 943:             break;
 944:         if (ign_some && c != ')')
 945:             continue;
 946:         n = 1;
 947:         if (c >= '0' && c <= '9') {
 948:             n = 0;
 949:             while (c >= '0' && c <= '9') {
 950:                 n = n * 10 + (c - '0');
 951:                 c = *fmt++;
 952:             }
 953:         }
 954:         switch (c) {
 955:         case '(':
 956:             if (w->w_next != fwind) /* Not bottom window. */
 957:                 ign_some = YES;
 958:             break;
 959: 
 960:         case ')':
 961:             ign_some = NO;
 962:             break;
 963: 
 964:         case '[':
 965:         case ']':
 966:             {
 967:                 char    *strs = (c == '[') ? "[[[[[[[[[[" : "]]]]]]]]]]";
 968: 
 969:                 mode_app(strs + 10 - RecDepth);
 970:             break;
 971:             }
 972: 
 973: #ifdef UNIX
 974:         case 'C':   /* check mail here */
 975:             if (chkmail(NO))
 976:                 mode_app("[New mail]");
 977:             break;
 978: 
 979: #endif /* UNIX */
 980: 
 981:         case 'M':
 982:             {
 983:                 static char *mmodes[] = {
 984:                 "Fundamental ",
 985:                 "Text ",
 986:                 "C ",
 987: #ifdef LISP
 988:                 "Lisp ",
 989: #endif
 990:                 0
 991:             };
 992: 
 993:                 mode_app(mmodes[thisbuf->b_major]);
 994: 
 995:             if (BufMinorMode(thisbuf, Fill))
 996:                 mode_app("Fill ");
 997:             if (BufMinorMode(thisbuf, Abbrev))
 998:                 mode_app("Abbrev ");
 999:             if (BufMinorMode(thisbuf, OverWrite))
1000:                 mode_app("OvrWt ");
1001:             if (BufMinorMode(thisbuf, Indent))
1002:                 mode_app("AI ");
1003:             if (InMacDefine)
1004:                 mode_app("Def ");
1005:             mode_p -= 1;    /* Back over the extra space. */
1006:             break;
1007:             }
1008: 
1009:         case 'c':
1010:             while (--n >= 0)
1011:                 *mode_p++ = fillc;
1012:             break;
1013: 
1014: #ifdef CHDIR
1015:         case 'd':   /* print working directory */
1016:             mode_app(pr_name(pwd(), YES));
1017:             break;
1018: #endif
1019: 
1020:         case 'e':
1021:             {
1022:             /* 2 space pad pluss padding for magic cookies */
1023:             char    *last_p = &line[CO - 2 - (2 * SG)];
1024: 
1025:             while (mode_p < last_p)
1026:                 *mode_p++ = fillc;
1027: 
1028:                 goto outahere;      /* %e means we're done! */
1029:             }
1030: 
1031:         case 'b':
1032:             mode_app(thisbuf->b_name);
1033:             break;
1034: 
1035:         case 'f':
1036:         case 'F':
1037:             if (thisbuf->b_fname == 0)
1038:                 mode_app("[No file]");
1039:             else {
1040:                 if (c == 'f')
1041:                     mode_app(pr_name(thisbuf->b_fname, YES));
1042:                 else
1043:                     mode_app(basename(thisbuf->b_fname));
1044:             }
1045:             break;
1046: 
1047: #ifdef LOAD_AV
1048:         case 'l':
1049:             {
1050:             double  theavg;
1051:                 char    minibuf[10];
1052: 
1053:                 get_la(&theavg);
1054:                 theavg += .005; /* round to nearest .01 */
1055:                 sprintf(minibuf, "%d.%02d",
1056:                    (int) theavg,
1057:                    (int)((theavg - (int) theavg) * 100));
1058:                 mode_app(minibuf);
1059:             break;
1060:             }
1061: #endif
1062: 
1063:         case 'm':
1064:             if (IsModified(w->w_bufp))
1065:                 *mode_p++ = fmt[0];
1066:             else
1067:                 *mode_p++ = fmt[1];
1068:             fmt += 2;   /* skip two characters */
1069:             break;
1070: 
1071:         case 'n':
1072:             {
1073:             char    tmp[16];
1074:             for (bp = world, n = 1; bp != 0; bp = bp->b_next, n++)
1075:                 if (bp == thisbuf)
1076:                     break;
1077: 
1078:             sprintf(tmp, "%d", n);
1079:             mode_app(tmp);
1080:             break;
1081:             }
1082: 
1083: #ifdef IPROCS
1084:         case 'p':
1085:             if (thisbuf->b_type == B_PROCESS) {
1086:             char    tmp[40];
1087: 
1088:             sprintf(tmp, "(%s)", (thisbuf->b_process == 0) ?
1089:                          "No process" :
1090:                          pstate(thisbuf->b_process));
1091:             mode_app(tmp);
1092:             break;
1093:             }
1094: #endif
1095: 
1096:         case 's':
1097:             if (mode_p[-1] == ' ')
1098:                 continue;
1099:             *mode_p++ = ' ';
1100:             break;
1101: 
1102:         case 't':
1103:             {
1104:             char    timestr[12];
1105: 
1106:                 mode_app(get_time((time_t *) 0, timestr, 11, 16));
1107:             break;
1108:             }
1109: 
1110:         case 'w':
1111:             if (w->w_LRscroll > 0)
1112:                 mode_app(">");
1113:         }
1114:     }
1115: 
1116: outahere:
1117:     *mode_p = 0;
1118: 
1119:     /* Highlight mode line. */
1120:     if (BriteMode) {
1121: #ifdef ID_CHAR
1122:         if (IN_INSmode)
1123:             INSmode(0);
1124: #endif
1125: #ifdef TERMCAP
1126:         putpad(SO, 1);
1127: #else
1128:         SO_on();
1129: #endif /* TERMCAP */
1130:     }
1131:     if (swrite(line, BriteMode, YES))
1132:         do_cl_eol(i_line);
1133:     else
1134:         UpdModLine = 1;
1135:     if (BriteMode)
1136: #ifdef TERMCAP
1137:         putpad(SE, 1);
1138: #else
1139:         SO_off();
1140: #endif /* TERMCAP */
1141: }
1142: 
1143: /* This tries to place the current line of the current window in the
1144:    center of the window, OR to place it at the arg'th line of the window.
1145:    This also causes the horizontal position of the line to be centered,
1146:    if the line needs scrolling, or moved all the way back to the left,
1147:    if that's possible. */
1148: void
1149: RedrawDisplay()
1150: {
1151:     int line;
1152:     Line    *newtop = prev_line((curwind->w_line = curline), is_an_arg() ?
1153:                 arg_value() : HALF(curwind));
1154: 
1155:     if ((line = in_window(curwind, curwind->w_line)) != -1)
1156:         PhysScreen[line].s_offset = -1;
1157:     if (newtop == curwind->w_top)
1158:         v_clear(FLine(curwind), FLine(curwind) + SIZE(curwind));
1159:     else
1160:         SetTop(curwind, newtop);
1161: }
1162: 
1163: void
1164: v_clear(line1, line2)
1165: register int    line1;
1166: {
1167:     register struct scrimage    *phys_p, *des_p;
1168: 
1169:     phys_p = &PhysScreen[line1];
1170:     des_p = &DesiredScreen[line1];
1171: 
1172:     while (line1 <= line2) {
1173:         i_set(line1, 0);
1174:         cl_eol();
1175:         phys_p->s_id = des_p->s_id = 0;
1176:         phys_p += 1;
1177:         des_p += 1;
1178:         line1 += 1;
1179:     }
1180: }
1181: 
1182: void
1183: ClAndRedraw()
1184: {
1185:     cl_scr(YES);
1186: }
1187: 
1188: void
1189: NextPage()
1190: {
1191:     Line    *newline;
1192: 
1193:     if (Asking)
1194:         return;
1195:     if (arg_value() < 0) {
1196:         negate_arg_value();
1197:         PrevPage();
1198:         return;
1199:     }
1200:     if (arg_type() == YES)
1201:         UpScroll();
1202:     else {
1203:         if (in_window(curwind, curwind->w_bufp->b_last) != -1) {
1204:             rbell();
1205:             return;
1206:         }
1207:         newline = next_line(curwind->w_top, max(1, SIZE(curwind) - 1));
1208:         SetTop(curwind, curwind->w_line = newline);
1209:         if (curwind->w_bufp == curbuf)
1210:             SetLine(newline);
1211:     }
1212: }
1213: 
1214: #ifdef MSDOS        /* kg */
1215: 
1216: void
1217: PageScrollUp()
1218: {
1219:     int i, n;
1220: 
1221:     n = max(1, SIZE(curwind) - 1);
1222:     for (i=0; i<n; i++) {
1223:         UpScroll();
1224:         redisplay();
1225:     }
1226: }
1227: 
1228: void
1229: PageScrollDown()
1230: {
1231:     int i, n;
1232: 
1233:     n = max(1, SIZE(curwind) - 1);
1234:     for (i=0; i<n; i++) {
1235:         DownScroll();
1236:         redisplay();
1237:     }
1238: }
1239: #endif /* MSDOS */
1240: 
1241: void
1242: PrevPage()
1243: {
1244:     Line    *newline;
1245: 
1246:     if (Asking)
1247:         return;
1248:     if (arg_value() < 0) {
1249:         negate_arg_value();
1250:         NextPage();
1251:         return;
1252:     }
1253:     if (arg_type() == YES)
1254:         DownScroll();
1255:     else {
1256:         newline = prev_line(curwind->w_top, max(1, SIZE(curwind) - 1));
1257:         SetTop(curwind, curwind->w_line = newline);
1258:         if (curwind->w_bufp == curbuf)
1259:             SetLine(newline);
1260:     }
1261: }
1262: 
1263: void
1264: UpScroll()
1265: {
1266:     SetTop(curwind, next_line(curwind->w_top, arg_value()));
1267:     if ((curwind->w_bufp == curbuf) &&
1268:         (in_window(curwind, curline) == -1))
1269:         SetLine(curwind->w_top);
1270: }
1271: 
1272: void
1273: DownScroll()
1274: {
1275:     SetTop(curwind, prev_line(curwind->w_top, arg_value()));
1276:     if ((curwind->w_bufp == curbuf) &&
1277:         (in_window(curwind, curline) == -1))
1278:         SetLine(curwind->w_top);
1279: }
1280: 
1281: int VisBell = NO,
1282:     RingBell = NO;  /* So if we have a lot of errors ...
1283: 			   ring the bell only ONCE */
1284: void
1285: rbell()
1286: {
1287:     RingBell = YES;
1288: }
1289: 
1290: /* Message prints the null terminated string onto the bottom line of the
1291:    terminal. */
1292: 
1293: void
1294: message(str)
1295: char    *str;
1296: {
1297:     if (InJoverc)
1298:         return;
1299:     UpdMesg = YES;
1300:     errormsg = NO;
1301:     if (str != mesgbuf)
1302:         null_ncpy(mesgbuf, str, (sizeof mesgbuf) - 1);
1303: }
1304: 
1305: /* End of Window */
1306: 
1307: void
1308: Eow()
1309: {
1310:     if (Asking)
1311:         return;
1312:     SetLine(next_line(curwind->w_top, SIZE(curwind) - 1 -
1313:             min(SIZE(curwind) - 1, arg_value() - 1)));
1314:     if (!is_an_arg())
1315:         Eol();
1316: }
1317: 
1318: /* Beginning of Window */
1319: 
1320: void
1321: Bow()
1322: {
1323:     if (Asking)
1324:         return;
1325:     SetLine(next_line(curwind->w_top, min(SIZE(curwind) - 1, arg_value() - 1)));
1326: }
1327: 
1328: private int LineNo,
1329:         last_col,
1330:         DoAutoNL;
1331: private Window  *old_wind;  /* save the window we were in BEFORE
1332: 				   before we were called, if UseBuffers
1333: 				   is nonzero */
1334: 
1335: int UseBuffers = FALSE;
1336: int TOabort = 0;
1337: 
1338: /* This initializes the typeout.  If send-typeout-to-buffers is set
1339:    the buffer NAME is created (emptied if it already exists) and output
1340:    goes to the buffer.  Otherwise output is drawn on the screen and
1341:    erased by TOstop() */
1342: 
1343: void
1344: TOstart(name, auto_newline)
1345: char    *name;
1346: {
1347:     if (UseBuffers) {
1348:         old_wind = curwind;
1349:         pop_wind(name, YES, B_SCRATCH);
1350:     } else
1351:         DisabledRedisplay = YES;
1352:     TOabort = LineNo = last_col = 0;
1353:     DoAutoNL = auto_newline;
1354: }
1355: 
1356: /* VARARGS1 */
1357: 
1358: void
1359: Typeout(fmt, va_alist)
1360: char    *fmt;
1361: va_dcl
1362: {
1363:     if (TOabort)
1364:         return;
1365: 
1366:     if (!UseBuffers && (LineNo == ILI - 1)) {
1367:         register int    c;
1368: 
1369:         LineNo = 0;
1370:         last_col = 0;
1371:         f_mess("--more--");
1372:         if ((c = getchar()) != ' ') {
1373:             TOabort = YES;
1374:             if (c != AbortChar && c != RUBOUT)
1375:                 Ungetc(c);
1376:             f_mess(NullStr);
1377:             return;
1378:         }
1379:         f_mess(NullStr);
1380:     }
1381: 
1382:     if (fmt) {
1383:         extern int  i_col;
1384:         char    string[132];
1385:         va_list ap;
1386: 
1387:         va_start(ap);
1388:         format(string, sizeof string, fmt, ap);
1389:         va_end(ap);
1390:         if (UseBuffers)
1391:             ins_str(string, NO);
1392:         else {
1393:             i_set(LineNo, last_col);
1394:             (void) swrite(string, NO, YES);
1395:             last_col = i_col;
1396:         }
1397:     }
1398:     if (!UseBuffers) {
1399:         PhysScreen[LineNo].s_id = -1;
1400:         if (fmt == 0 || DoAutoNL == YES) {
1401:             cl_eol();
1402:             flusho();
1403:             LineNo += 1;
1404:             last_col = 0;
1405:         }
1406:     } else if (fmt == 0 || DoAutoNL != 0)
1407:         ins_str("\n", NO);
1408: }
1409: 
1410: void
1411: TOstop()
1412: {
1413:     int c;
1414: 
1415:     if (UseBuffers) {
1416:         ToFirst();
1417:         SetWind(old_wind);
1418:     } else {
1419:         if (TOabort) {
1420:             DisabledRedisplay = NO;
1421:             return;
1422:         }
1423:         if (last_col != 0)
1424:             Typeout((char *) 0);
1425:         Typeout("----------");
1426:         cl_eol();
1427:         flusho();
1428:         c = getchar();
1429:         if (c != ' ')
1430:             Ungetc(c);
1431:         DisabledRedisplay = NO;
1432:     }
1433: }

Defined functions

AddLines defined in line 454; used 3 times
ChkWindows defined in line 77; used 3 times
DeTab defined in line 598; used 3 times
DelChar defined in line 755; used 3 times
DelLines defined in line 475; used 3 times
DoIDline defined in line 230; used 5 times
GotoDot defined in line 425; used 3 times
IDchar defined in line 645; used 3 times
IDcomp defined in line 701; used 2 times
INSmode defined in line 586; used 6 times
InsChar defined in line 782; used 3 times
ModeLine defined in line 903; used 3 times
NumSimilar defined in line 688; used 1 times
OkayDelete defined in line 723; used 1 times
OkayInsert defined in line 733; used 1 times
PageScrollUp defined in line 1216; used 4 times
UntilEqual defined in line 435; used 4 times
UpScroll defined in line 1263; used 7 times
UpdLine defined in line 498; used 3 times
UpdWindow defined in line 287; used 3 times
chkmail defined in line 845; used 2 times
disp_opt_init defined in line 573; used 1 times
do_cl_eol defined in line 546; used 6 times
dobell defined in line 179; used 5 times
find_pos defined in line 199; used 3 times
mode_app defined in line 890; used 20 times
v_clear defined in line 1163; used 3 times

Defined variables

BiffChk defined in line 842; used 1 times
BriteMode defined in line 888; used 7 times
CElen defined in line 571; used 2 times
DClen defined in line 566; used 2 times
DisabledRedisplay defined in line 71; used 4 times
DoAutoNL defined in line 1330; used 3 times
IClen defined in line 568; used 2 times
IMlen defined in line 570; used 3 times
IN_INSmode defined in line 562; used 12 times
LineNo defined in line 1328; used 6 times
MDClen defined in line 567; used 2 times
MIClen defined in line 569; used 2 times
MailInt defined in line 840; used 3 times
Mailbox defined in line 839; used 2 times
ModeFmt defined in line 901; used 2 times
ScrollAll defined in line 285; used 3 times
TOabort defined in line 1336; used 4 times
UseBuffers defined in line 1335; used 6 times
UseIC defined in line 564; used 4 times
VisBell defined in line 1281; used 2 times
last_col defined in line 1329; used 6 times
mend_p defined in line 887; used 3 times
mode_p defined in line 886; used 15 times
old_wind defined in line 1331; used 2 times
private defined in line 1328; never used

Defined macros

OkayOut defined in line 608; used 5 times
private defined in line 68; used 20 times
Last modified: 1988-08-25
Generated: 2016-12-26
Generated by src2html V0.67
page hit count: 2996
Valid CSS Valid XHTML 1.0 Strict