/* * Copyright 1998, Dave Cridland + Stan Brooks * This file is part of SLirc. * Redistributable under GPL or PAL, or both, at your discretion. * See COPYING. */ /* * This was all based on a series of discussions between me and Stan. * I just happened to write the code out before Stan could. :-) */ #include #include #include #include #include #include #include #include "slirc.h" typedef struct _LineData_Type { char * from; char * to; char * server; char * text; unsigned char indent; unsigned char bold; unsigned char type; unsigned char htyp; /* int allwin; */ unsigned int rxtime; unsigned int msgno; /* increasing, unique message number */ /* int real_line; */ struct _LineData_Type * lprev; struct _LineData_Type * lnext; } LineData_Type; typedef struct _Line_Type { LineData_Type * line; struct _Line_Type * prev; struct _Line_Type * next; } Line_Type; typedef struct _WindowMatch_Type { char * from; char * to; char * server; struct _WindowMatch_Type * prev; struct _WindowMatch_Type * next; } WindowMatch_Type; typedef struct _Window_Type { char * name; WindowMatch_Type * start_match; /* association of line to window is OR of linked-list of things */ WindowMatch_Type * end_match; Line_Type * startline; Line_Type * currentline; Line_Type * auxline; /* cached location from last PutIntoWindow() */ Line_Type * endline; int dirty; unsigned int lastseen; struct _Window_Type * prev; struct _Window_Type * next; } Window_Type; typedef struct _WindowList_Type { Window_Type * start; Window_Type * end; Window_Type * current; Window_Type * mini; } WindowList_Type; /* typedef struct _Transcript_Type { LineData_Type * start; LineData_Type * end; } Transcript_Type; * static Transcript_Type Transcript; */ /* might later want to subdivide this master list by servers */ static LineData_Type * LD_first=NULL; static LineData_Type * LD_last=NULL; static WindowList_Type WindowList; /* sjb: IsVisible() may be resurrected -- i still want regexp selection on windows */ static int IsVisible(LineData_Type *line) { return 1; } /* * String drawing stuff... */ static int HeadCol(int type) { if (!UseColours) return 0; switch(type) { case IRC_TYPE_LOG: return ColLogText; case IRC_TYPE_INFO: return ColInfoText; case IRC_TYPE_ACTION: return ColSayNormText; case IRC_TYPE_SAY: return ColSayNormText; default: return ColDefault; } } static char *WordBreak(char **cp, char *txth, int tw) /* * a helper function for breaking input text into pieces * of width no more than tw chars, trying to break on a word-boundary * It skips the start-pointer *cp past initial whitespace, * and returns a pointer to the 'best' place to line-break. * Note: txth should point to the terminating '\0' of the * string (*cp), this is just to avoid re-computing it each time. */ { char *p,*q; p = *cp; /* skip p forward to non-whitespace */ while (*p && isspace(*p)) p++ ; q = p; if (*p) { q += tw; if (q > txth) q = txth; /* go back up to 1/3 of text-width looking for a word-break */ if (*q && !isspace(*q)) { char *r,*rlow; rlow = p+(2*tw)/3; /* don't backup any lower than this to find a word-break */ for (r = q; --r > rlow; ) if (isspace(*r)) { q = r; break; } } } *cp = p; return q; } /* * Preformat_Lines(bottom,nrows) * This parses out nrow lines working backwards from bottom * doing line-breaks, etc, for displaying on visible screen * It necessary to reformat each time at actual * time of display, because the screen may have been resized, * which would change (1) the location of line-breaks, * and (2) the number of screen-lines taken to display * each .text field. * Also, keeping each .text field un-broken in the main * lists(s) will considerably simplify regexp selection * on the .text field. */ static LineData_Type *Preformat_Lines(Line_Type *bottom,int nrows) { Line_Type *lt; LineData_Type *line,*l0,*last; int lct = 0; l0 = NULL; last = NULL; for (lt = bottom; lctprev) { LineData_Type *l,*l1,*l2; char *from, *to; char *p, *q, *text, *txth; int indent, bold, tw, ltyp, type; line = lt->line; if (!IsVisible(line)) continue; bold = 0; type = line->type; ltyp = type; /* Calculate indent for any continued lines */ indent = 3; if ((from = line->from)) indent += strlen(from); if ((to = line->to)) if (IrcCmp(to, Target, -1) && IrcCmp(to, NickName, -1)) indent += strlen(to) + 2; /* tw is text-width for any contin'd lines */ tw = SLtt_Screen_Cols - indent; text = line->text; txth = text + strlen(text); while (--txth >= text && isspace(*txth)); txth++; if (txth == text) continue; /* line was nothing but whitespace */ l2 = l0; l = l0 = l1 = NULL; for (p = text; p < txth; p = q) { q = WordBreak(&p,txth,tw); if (q == p) break; /* nothing left but whitespace */ l = (LineData_Type *) SLmalloc(sizeof(LineData_Type)); if (!l) Fatal("malloc()"); memset((char *) l, 0, sizeof(LineData_Type)); /* this inits type=0 */ l->text = SLmake_nstring(p,q - p); if (!l->text) Fatal("malloc()"); l->lprev = l1; l->lnext = l2; if (l2) l2->lprev = l; if (l1) l1->lnext = l; l1 = l; if (!l0) l0 = l; l->from = from; l->to = to; l->indent = indent; l->bold = bold; l->htyp = type; /* info for scrolls that break line */ l->type = ltyp; /* set bold for cont-line */ while(pnrows) { line = l0; l0 = l0->lnext; l0->lprev = NULL; SLfree(line->text); SLfree((char*)line); lct--; } return last; } static void DrawString(char* text, int bold, int ColNorm, int ColBold) { char* cp = text; SLsmg_set_color(ColNorm); for (cp = text; *cp; cp++) { if (*cp == CTRL_B) { if ((bold ^= 1)) SLsmg_set_color(ColBold); else SLsmg_set_color(ColNorm); } else if (*cp == CTRL_O) { bold = 0; SLsmg_set_color(ColNorm); } else if (*cp != '') SLsmg_write_char(*cp); } } static void DrawLineData(int row, LineData_Type * line) { int i; SLsmg_gotorc(row, 0); if(UseColours) SLsmg_set_color(ColDefault); if (line) { switch (line->type) { case IRC_TYPE_INFO: if (UseColours) SLsmg_set_color(ColLogTag); SLsmg_write_string("++ "); DrawString(line->text, line->bold, HeadCol(line->htyp), ColBufferTextBold); break; case IRC_TYPE_LOG: if (UseColours) SLsmg_set_color(ColLogTag); SLsmg_write_string("// "); DrawString(line->text, line->bold, HeadCol(line->htyp), ColBufferTextBold); break; case IRC_TYPE_ACTION: if (UseColours) SLsmg_set_color(ColSayNormDec); SLsmg_write_string(">>"); if (UseColours) SLsmg_set_color(ColSayNormSend); SLsmg_write_string(line->from); { int ColNorm = HeadCol(line->htyp); SLsmg_set_color(ColNorm); SLsmg_write_char(' '); DrawString(line->text, line->bold, ColNorm, ColBufferTextBold); } break; case IRC_TYPE_SAY: if (UseColours) SLsmg_set_color(ColSayNormDec); SLsmg_write_string("<"); if (UseColours) { if (!IrcCmp(line->to, NickName, -1)) SLsmg_set_color(ColSayTgtoSend); else if (!IrcCmp(line->text, NickName, strlen(NickName)) || !IrcCmp(line->from, NickName, -1) ) SLsmg_set_color(ColAutoResp); else SLsmg_set_color(ColSayNormSend); } SLsmg_write_string(line->from); if (IrcCmp(line->to, Target, -1) && IrcCmp(line->to, NickName, -1)) { if (UseColours) SLsmg_set_color(ColSayNormDec); SLsmg_write_string("->"); if (UseColours) SLsmg_set_color(ColSayTgtoSend); SLsmg_write_string(line->to); } if (UseColours) SLsmg_set_color(ColSayNormDec); SLsmg_write_string("> "); DrawString(line->text, line->bold, HeadCol(line->htyp), ColBufferTextBold); break; case IRC_TYPE_CONT: for (i = 0; i < line->indent; i++) SLsmg_write_char(' '); DrawString(line->text, line->bold, HeadCol(line->htyp), ColBufferTextBold); break; default: SLsmg_write_string("!! "); SLsmg_write_string("Unknown line type."); } /* switch */ if (UseColours) SLsmg_set_color(ColDefault); } /* if (line) */ SLsmg_erase_eol(); } static int DrawWindow(Window_Type * window, int startrow, int length) { int n=0; if(window && window->dirty) { Line_Type * lt; LineData_Type *line; n=1; /* if(UseColours) SLsmg_set_color(ColDefault); SLsmg_fill_region(startrow, 0, length, SLtt_Screen_Cols, ' '); */ if(!(lt=window->currentline)) lt=window->endline; if (lt) window->lastseen=lt->line->msgno; /* Preformat with appropriate line-breaks and indentation, * returning a temporary list of formatted lines ready to * be displayed by DrawLineData() */ line=Preformat_Lines(lt,length); for( ; length>0; length--) { LineData_Type *l0; DrawLineData(startrow+length-1, line); if (line==NULL) continue; l0 = line->lprev; SLfree(line->text); /* free that trash */ SLfree((char*)line); line = l0; } window->dirty=0; } return n; } int DrawAllWindows(int start_row, int lines) { int n=0; if(WindowList.mini && (WindowList.mini != WindowList.current)) { n += DrawWindow(WindowList.mini, start_row, 4); SLsmg_gotorc(start_row + 4, 0); SLsmg_set_color(UseColours? ColStatusSay : 1); SLsmg_erase_eol(); SLsmg_write_string(" ["); SLsmg_write_string(WindowList.mini->name); SLsmg_write_string("]"); start_row+=5; lines-=5; SLsmg_set_color(ColDefault); } n += DrawWindow(WindowList.current, start_row, lines); return n; } static void ChangeWindow(Window_Type * window) { WindowList.current=window; window->dirty=1; window->currentline=NULL; if(WindowList.mini) WindowList.mini->dirty=1; } static Window_Type * FindWindow(char * name) { Window_Type * window; for(window=WindowList.end; window; window=window->prev) { if(!IrcCmp(name, window->name, -1)) break; } return(window); } /* * New matching driver thing. * The old way was to have multiple window defs, but that was a right old hack. */ static int WindowAddMatch(Window_Type * window, char * to_match, char * from_match, char * server_match) { WindowMatch_Type * match; if(!window) return(1); match = (WindowMatch_Type *)SLmalloc(sizeof(WindowMatch_Type)); memset(match, 0, sizeof(WindowMatch_Type)); if(!match) return(2); if(from_match[0]) match->from = SLmake_string(from_match); if(to_match[0]) match->to = SLmake_string(to_match); if(server_match[0]) match->server = SLmake_string(server_match); if(window->start_match==NULL) window->start_match = window->end_match = match; else { window->end_match->next = match; match->prev = window->end_match; window->end_match = match; } return(0); } static Window_Type * NewWindow(char * name, char * to_match, char * from_match, char * server_match) { Window_Type * window; if(!(window=FindWindow(name))) { window=(Window_Type *)SLmalloc(sizeof(Window_Type)); memset((char *)window, 0, sizeof(Window_Type)); /* silent assumption that this initializes pointer fields to NULL */ window->name=SLmake_string(name); } WindowAddMatch(window, to_match, from_match, server_match); return(window); } /* * Which window should get this line? */ static Window_Type * WhichWindow(LineData_Type * line) { Window_Type * window; WindowMatch_Type * match; for(window=WindowList.end; window; window=window->prev) for(match=window->end_match; match; match=match->prev) if(!match->server || (line->server && !IrcCmp(match->server, line->server, -1))) if(!match->from || (line->from && !IrcCmp(match->from, line->from, -1))) if(!match->to || (line->to && !IrcCmp(match->to, line->to, -1))) return(window); return NULL; } /* * Puts a LineData_Type object into a window. * Note that most of this code is handling lines added * out of order, which should only happen when a window is * deleted. * sjb: this looked cpu-intensive to search whole list * to find successive msgno's... So, modified it to cache * last added line as window->auxline, and starting successive * searches from that point, since additions (after a window delete) * will generally be in order. */ static Window_Type * PutIntoWindow(LineData_Type * line) { Window_Type * window = WhichWindow(line); Line_Type *lt,*xlt; /* create a Line_Type *lt for this line */ lt=(Line_Type *)SLmalloc(sizeof(Line_Type)); memset((char *)lt, 0, sizeof(Line_Type)); lt->line=line; if((xlt=window->endline)) { unsigned int msgno = line->msgno; /* sjb: next loop is (should be) redundant */ /* while(window->endline->next) */ /* window->endline=window->endline->next; */ if(xlt->line->msgno>msgno) { /* doesn't go last in list, so start search from window->auxline if set */ if(window->auxline) xlt = window->auxline; while(xlt->line->msgnonext) xlt=xlt->next; while(xlt->line->msgno>msgno && xlt->prev) xlt=xlt->prev; } if(xlt->line->msgnonext = xlt->next)) window->endline=lt; else xlt->next->prev=lt; lt->prev = xlt; xlt->next = lt; } else { /* insert before xlt */ if(!(lt->prev = xlt->prev)) window->startline=lt; else xlt->prev->next=lt; lt->next = xlt; xlt->prev = lt; } } else window->startline=window->endline=lt; window->auxline=lt; /* cache last insertion point */ window->dirty=1; return(window); } static Window_Type * AddNewWindow(char * name, char * to, char * from, char * server) { Window_Type * old_window; Window_Type * new_window; LineData_Type ltmp; /* * Setup dummy line ltmp with suitable data. */ memset((char *)<mp, 0, sizeof(LineData_Type)); ltmp.from=from[0]? from:"*"; ltmp.to=to[0]? to:"*"; ltmp.server=server[0]? server:"*"; /* * Okay, that's a dummy line setup. * Note that we don't actually need to malloc those strings properly, * because ltmp is just used locally and forgotten... */ old_window=WhichWindow(<mp); /* this can be NULL? */ new_window=NewWindow(name, to, from, server); /* * If this was a ruleset addition, rather than a window, then we already * have the window in the list... */ if(!FindWindow(name)) { new_window->prev=old_window; new_window->next=old_window->next; old_window->next=new_window; if(new_window->next) new_window->next->prev=new_window; if(WindowList.end==old_window) WindowList.end=new_window; } /* * Okay. Now we need to reshuffle the old window's lines, because some of those * lines might be better placed in the new window. */ if(old_window->startline) { /* take care, because lines can go right back into old_window */ Line_Type *lt,*nlt; lt=old_window->startline; old_window->startline=old_window->endline=old_window->auxline=NULL; for( ; lt; lt=nlt) { nlt=lt->next; PutIntoWindow(lt->line); SLfree((char *)lt); } old_window->currentline=NULL; } return(new_window); } /* * Stan! Stan! Stan! * This is probably all wrong. * But then, I wrote it. * - dwd */ static char * SLirc_WhichWindow(char * to, char * from, char * server) { Window_Type * win; LineData_Type ltmp; memset((char *)<mp, 0, sizeof(LineData_Type)); ltmp.from=from[0]? from:"*"; ltmp.to=to[0]? to:"*"; ltmp.server=server[0]? server:"*"; win = WhichWindow(<mp); return(win->name); } static void DeleteWindow(char * name) { Window_Type * old_window=FindWindow(name); if(!old_window || !old_window->prev) return; /* Don't delete the default window. */ old_window->prev->next=old_window->next; if(old_window->next) old_window->next->prev=old_window->prev; else WindowList.end=old_window->prev; /* * Now resort this window into the windows that are left... */ if((old_window->startline)) { Line_Type *lt, *nlt; for(lt=old_window->startline; lt; lt=nlt) { nlt=lt->next; PutIntoWindow(lt->line); SLfree((char *)lt); } old_window->startline=old_window->endline=NULL; } SLfree((char *)old_window); /* sjb: memory leak -- need to free .name */ while((old_window=FindWindow(name))) { if(!old_window->prev) return; /* Don't delete the default window. */ old_window->prev->next=old_window->next; if(old_window->next) old_window->next->prev=old_window->prev; else WindowList.end=old_window->prev; SLfree((char *)old_window); } } static void scroll_n(Window_Type * window, int n) { Line_Type * l; l=window->currentline; if(!l) l=window->endline; if(!n || !l) return; if(n>0) while(n && l->next) { l = l->next; if (IsVisible(l->line)) n--; } else while(n && l->prev) { l = l->prev; if (IsVisible(l->line)) n++; } if (window->currentline != l) { window->currentline=l; window->dirty=1; } } static void scroll_x(int *x) { scroll_n(WindowList.current, *x); } static void scroll_eob(void) { Window_Type *w; w=WindowList.current; if (w->currentline != w->endline) { w->currentline=w->endline; w->dirty=1; } } static void scroll_bob(void) { Window_Type *w; w=WindowList.current; if (w->currentline != w->startline) { w->currentline=w->startline; w->dirty=1; } } void DirtyWindow(void) { WindowList.current->dirty=1; if(WindowList.mini) WindowList.mini->dirty=1; } /* * this next is the place where all things enter the windoze lists, * that is, become LineData_Type objects */ void * SLirc_Multn(char * text, char * from, char * to, unsigned int type) { static int MsgNo=0; LineData_Type *line; Window_Type *w; line = (LineData_Type *) SLmalloc(sizeof(LineData_Type)); if (!line) Fatal("malloc()"); memset((char *) line, 0, sizeof(LineData_Type)); /* * now put this new line in the master list * BTW, the 'master list' doesn't currently do much * of anything, but since there's an lprev,lnext field * it may as well be used... we might want to free things * one day, or dump it all to a file or sth */ line->lprev = LD_last; line->lnext = NULL; if (!LD_first) LD_first = line; else LD_last->lnext = line; LD_last = line; /* fill in line's data fields */ if ((line->from = from)) if (!(line->from = SLmake_string(from))) Fatal("malloc()"); if ((line->to = to)) if (!(line->to = SLmake_string(to))) Fatal("malloc()"); if ((line->text = text)) if (!(line->text = SLmake_string(text))) Fatal("malloc()"); if ((line->server = ServerName)) if (!(line->server = SLmake_string(ServerName))) Fatal("malloc()"); line->type = type; line->msgno = MsgNo++; line->rxtime=time(NULL); w=PutIntoWindow(line); scroll_n(w, 1); w->dirty=1; return(line); } static void SLirc_ChangeMini(char * name) { WindowList.mini=FindWindow(name); WindowList.current->dirty=1; if(WindowList.mini) WindowList.mini->dirty=1; } static void SLirc_ChangeWindow(char * name) { Window_Type *w; w=FindWindow(name); if(w) ChangeWindow(w); } static int IsUnread(Window_Type * w) { return (w->endline && w->endline->line->msgno > w->lastseen); } static char * SLirc_GetWindowList(void) { static char buf[1024]; /* buffer overflow */ char * cp = buf; Window_Type * w; *cp=0; for(w=WindowList.start; w; w=w->next) { sprintf(cp, "%c%s ", IsUnread(w)?'*':'-', w->name); cp += strlen(cp); } return(buf); } static SLang_Intrin_Fun_Type WindowsFunctions[] = { MAKE_INTRINSIC_I("irc_scroll", scroll_x, SLANG_VOID_TYPE), MAKE_INTRINSIC_0("irc_eob", scroll_eob, SLANG_VOID_TYPE), MAKE_INTRINSIC_0("irc_bob", scroll_bob, SLANG_VOID_TYPE), MAKE_INTRINSIC_S("irc_change_window", SLirc_ChangeWindow, SLANG_VOID_TYPE), MAKE_INTRINSIC_N("irc_create_window", AddNewWindow, SLANG_VOID_TYPE, 4, SLANG_STRING_TYPE, SLANG_STRING_TYPE, SLANG_STRING_TYPE, SLANG_STRING_TYPE, 0, 0, 0), MAKE_INTRINSIC_S("irc_destroy_window", DeleteWindow, SLANG_VOID_TYPE), MAKE_INTRINSIC_S("irc_change_mini", SLirc_ChangeMini, SLANG_VOID_TYPE), MAKE_INTRINSIC_SSS("irc_which_window", SLirc_WhichWindow, SLANG_STRING_TYPE), MAKE_INTRINSIC_0("irc_get_winlist", SLirc_GetWindowList, SLANG_STRING_TYPE), SLANG_END_TABLE }; void Init_Windows() { SLadd_intrin_fun_table(WindowsFunctions, "_Windoze"); WindowList.start=WindowList.end=WindowList.current=NewWindow("Default", "", "", ""); WindowList.mini=NULL; }