/*
* 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 <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <slang.h>
#include <time.h>
#include <string.h>
#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; lct<nrows && lt ; lt = lt->prev) {
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(p<q) {
char ch;
ch = *p++;
if (ch == CTRL_B) bold ^= 1;
else if (ch == CTRL_O) bold = 0;
}
ltyp = IRC_TYPE_CONT;
lct++;
}
if (!last) last = l;
if (!l0) l0 = l2; /* restore l0 if no lines resulted */
}
/* now free any unneeded rows at the top, leaving precisely nrows */
while (lct>nrows) {
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->msgno<msgno && xlt->next) xlt=xlt->next;
while(xlt->line->msgno>msgno && xlt->prev) xlt=xlt->prev;
}
if(xlt->line->msgno<msgno)
{ /* insert after xlt */
if(!(lt->next = 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;
}
syntax highlighted by Code2HTML, v. 0.9.1