changeset 466:796508f828f6

Refactor much of the "windowing" UI code into a new module, ui.[ch]
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 26 May 2012 06:56:18 +0300
parents c3b3b6d89084
children 4b2f94b1a3c1
files Makefile.gen main.c ui.c ui.h util.c util.h
diffstat 6 files changed, 555 insertions(+), 468 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.gen	Sat May 26 06:18:19 2012 +0300
+++ b/Makefile.gen	Sat May 26 06:56:18 2012 +0300
@@ -17,7 +17,7 @@
 THLIBS_A=$(OBJPATH)thlibs.a
 THLIBS_OBJ=th_util.o th_string.o th_args.o th_config.o
 
-NNCHAT_OBJ=main.o util.o network.o
+NNCHAT_OBJ=main.o util.o network.o ui.o
 NNCHAT_BIN=$(BINPATH)nnchat$(EXEEXT)
 
 TARGETS+=$(THLIBS_A) $(NNCHAT_BIN)
--- a/main.c	Sat May 26 06:18:19 2012 +0300
+++ b/main.c	Sat May 26 06:56:18 2012 +0300
@@ -5,6 +5,7 @@
  */
 #include "util.h"
 #include "network.h"
+#include "ui.h"
 #include "th_args.h"
 #include "th_config.h"
 #include <errno.h>
@@ -16,11 +17,6 @@
 #else
 #include <sys/wait.h>
 #endif
-#ifdef HAVE_NCURSES_H
-#include <ncurses.h>
-#else
-#include <curses.h>
-#endif
 
 #ifdef __WIN32
 #define SET_CONFIG_FILE    "nnchat.txt"
@@ -37,7 +33,6 @@
 
 #define SET_MAX_HISTORY (16)        // Command history length
 #define SET_KEEPALIVE   (15*60)     // Ping/keepalive period in seconds
-#define SET_MAX_WINDOWS (32)
 
 
 /* Options
@@ -63,12 +58,6 @@
 BOOL    optDebug = FALSE;
 BOOL    optLogEnable = FALSE;
 
-nn_window_t *chatWindows[SET_MAX_WINDOWS],
-        *currWin = NULL;
-WINDOW  *mainWin = NULL,
-        *statusWin = NULL,
-        *editWin = NULL;
-
 qlist_t *setIgnoreList = NULL,
         *setIdleMessages = NULL;
 nn_userhash_t *nnUsers = NULL;
@@ -209,268 +198,6 @@
 }
 
 
-nn_window_t *nnwin_find(const char *id)
-{
-    int i;
-
-    for (i = 0; i < SET_MAX_WINDOWS; i++)
-        if (chatWindows[i] != NULL &&
-            chatWindows[i]->id != NULL &&
-            th_strcasecmp(id, chatWindows[i]->id) == 0)
-            return chatWindows[i];
-
-    return NULL;
-}
-
-
-BOOL nnwin_open(const char *name, BOOL curwin)
-{
-    int i;
-    nn_window_t *res;
-    if (name == NULL)
-        return FALSE;
-
-    if ((res = nn_window_new(name)) == NULL)
-        return FALSE;
-
-    for (i = 1; i < SET_MAX_WINDOWS; i++)
-        if (chatWindows[i] == NULL)
-        {
-            res->num = i;
-            chatWindows[i] = res;
-            if (curwin)
-                currWin = res;
-            return TRUE;
-        }
-
-    return FALSE;
-}
-
-
-void nnwin_close(nn_window_t *win)
-{
-    int i;
-    if (win == NULL) return;
-
-    for (i = 1; i < SET_MAX_WINDOWS; i++)
-        if (chatWindows[i] == win)
-        {
-            chatWindows[i] = NULL;
-            nn_window_free(win);
-            return;
-        }
-}
-
-
-void nnwin_update_statusline(void)
-{
-    char tmpStr[128];
-    int i;
-
-    if (statusWin == NULL) return;
-
-    str_get_timestamp(tmpStr, sizeof(tmpStr), "%H:%M:%S");
-
-    wbkgdset(statusWin, COLOR_PAIR(10));
-    werase(statusWin);
-
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
-    mvwaddstr(statusWin, 0, 1, tmpStr);
-
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
-    waddstr(statusWin, " | ");
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(16));
-    waddstr(statusWin, optUserName);
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
-
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
-    waddstr(statusWin, " | ");
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
-    snprintf(tmpStr, sizeof(tmpStr), "#%06x", optUserColor);
-    waddstr(statusWin, tmpStr);
-
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
-    waddstr(statusWin, " | WIN: ");
-    snprintf(tmpStr, sizeof(tmpStr), "%d: %s / %d",
-        currWin->num + 1,
-        currWin->id != NULL ? currWin->id : "MAIN",
-        currWin->pos);
-    waddstr(statusWin, tmpStr);
-
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
-    waddstr(statusWin, " | ");
-    wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
-
-    for (i = 0; i < SET_MAX_WINDOWS; i++)
-        if (chatWindows[i] != NULL && chatWindows[i]->dirty)
-        {
-            snprintf(tmpStr, sizeof(tmpStr), "%d ", i + 1);
-            waddstr(statusWin, tmpStr);
-        }
-
-    wrefresh(statusWin);
-}
-
-
-void nnwin_update_editbuf(nn_editbuf_t *buf)
-{
-    char *tmp;
-    if (editWin == NULL || buf == NULL) return;
-
-    buf->data[buf->len] = 0;
-    tmp = nn_username_decode(th_strdup(buf->data));
-
-    werase(editWin);
-
-    wattrset(editWin, A_NORMAL);
-
-    if (buf->pos < buf->len)
-    {
-        waddnstr(editWin, tmp, buf->pos);
-        wattrset(editWin, A_REVERSE);
-        waddch(editWin, tmp[buf->pos]);
-        wattrset(editWin, A_NORMAL);
-        waddnstr(editWin, tmp + buf->pos + 1, buf->len - buf->pos - 1);
-    }
-    else
-    {
-        waddnstr(editWin, tmp, buf->len);
-        wattrset(editWin, A_REVERSE);
-        waddch(editWin, ' ');
-        wattrset(editWin, A_NORMAL);
-    }
-    wrefresh(editWin);
-    th_free(tmp);
-}
-
-
-int nnwin_print(WINDOW *win, const char *fmt)
-{
-    const char *s = fmt;
-    int col = 0;
-
-    while (*s)
-    {
-        if (*s == '½')
-        {
-            s++;
-            if (*s == '½')
-            {
-                waddch(win, ((unsigned char) *s) | col);
-                s++;
-            }
-            else
-            {
-                memcpy(&col, s, sizeof(int));
-                s += sizeof(int);
-            }
-        }
-        else
-        {
-            waddch(win, ((unsigned char) *s) | col);
-            s++;
-        }
-    }
-    return 0;
-}
-
-
-#define QPUTCH(ch) th_vputch(&(win->buf), &(win->bufsize), &(win->len), ch)
-
-int nnwin_print_buf(nn_window_t *win, const char *fmt)
-{
-    const char *s = fmt;
-    int col = 0;
-    while (*s)
-    {
-        if (*s == '½')
-        {
-            s++;
-            if (*s == '½')
-            {
-                QPUTCH(*s);
-                QPUTCH(*s);
-                win->chlen++;
-            }
-            else
-            {
-                int val = 0;
-                while (*s >= '0' && *s <= '9')
-                {
-                    val *= 10;
-                    val += (*s - '0');
-                    s++;
-                }
-                if (*s != '½') return -1;
-
-                if (val < 9)
-                    col = A_DIM | COLOR_PAIR(val);
-                else if (val < 30)
-                    col = A_BOLD | COLOR_PAIR(val - 9);
-
-                QPUTCH('½');
-
-                if (!th_growbuf(&(win->buf), &(win->bufsize), &(win->len), sizeof(int)))
-                    return -2;
-
-                memcpy(win->buf + win->len, &col, sizeof(int));
-                win->len += sizeof(int);
-            }
-        }
-        else if (*s == '\n')
-        {
-            QPUTCH('\n');
-            QPUTCH(0);
-            th_ringbuf_add(win->data, win->buf);
-            win->buf = NULL;
-            win->chlen = 0;
-            win->dirty = TRUE;
-        }
-        else if (*s != '\r')
-        {
-            QPUTCH((unsigned char) *s == 255 ? ' ' : *s);
-            win->chlen++;
-        }
-
-        s++;
-    }
-
-    return 0;
-}
-
-
-BOOL nnwin_update_main(BOOL force)
-{
-    int h, offs;
-    qringbuf_t *buf;
-
-    // Check pointers
-    if (mainWin == NULL || currWin == NULL)
-        return FALSE;
-
-    // Check if update is forced or if the window is dirty
-    if (!force && !currWin->dirty)
-        return FALSE;
-
-    // Compute how many lines from backbuffer fit on the screen
-    buf = currWin->data;
-    h = getmaxy(mainWin);
-
-    // Clear and redraw window
-    werase(mainWin);
-    scrollok(mainWin, 1);
-    for (offs = buf->size - h - currWin->pos; offs >= 0 && offs < buf->size - currWin->pos && offs < buf->size; offs++)
-    {
-        if (buf->data[offs] != NULL)
-            nnwin_print(mainWin, buf->data[offs]);
-    }
-
-    currWin->dirty = FALSE;
-    wrefresh(mainWin);
-    return TRUE;
-}
-
-
 int printFile(FILE *outFile, const char *fmt)
 {
     const char *s = fmt;
@@ -522,7 +249,7 @@
 
     if (!optDaemon && (flags & LOG_WINDOW))
     {
-        nn_window_t *tmp = win != NULL ? win : chatWindows[0];
+        nn_window_t *tmp = (win != NULL) ? win : nnwin_main_window();
         if (flags & LOG_STAMP) nnwin_print_buf(tmp, tmpStr);
         nnwin_print_buf(tmp, buf);
     }
@@ -577,6 +304,7 @@
         errorMessages = tmp;
 }
 
+
 void errorMsg(const char *fmt, ...)
 {
     va_list ap;
@@ -586,12 +314,14 @@
     va_end(ap);
 }
 
+
 void errorFunc(struct _nn_conn_t *conn, const char *fmt, va_list ap)
 {
     (void) conn;
     errorMsgV(fmt, ap);
 }
 
+
 void messageFunc(struct _nn_conn_t *conn, const char *fmt, va_list ap)
 {
     (void) conn;
@@ -1028,10 +758,10 @@
     }
     else
     {
-        if (currWin != chatWindows[0])
+        if (currWin != nnwin_main_window())
         {
             nnwin_close(currWin);
-            currWin = chatWindows[0];
+            currWin = nnwin_main_window();
         }
         else
         {
@@ -1051,11 +781,9 @@
     if (arg[0])
     {
         int val = atoi(arg);
-        if (val >= 1 && val < SET_MAX_WINDOWS)
-        {
-            if (chatWindows[val - 1] != NULL)
-                currWin = chatWindows[val - 1];
-        }
+        nn_window_t *win = nnwin_get(val);
+        if (win != NULL)
+            currWin = win;
         else
         {
             printMsgQ(currWin, "Invalid window number '%s'\n", arg);
@@ -1296,7 +1024,7 @@
         return nn_handle_command(conn, buf);
 
     // If current window is not the main room window, send private
-    if (currWin != chatWindows[0])
+    if (currWin != nnwin_main_window())
     {
         if (currWin->id != NULL)
         {
@@ -1329,41 +1057,6 @@
 }
 
 
-void nnwin_close_windows(void)
-{
-    if (mainWin) delwin(mainWin);
-    if (statusWin) delwin(statusWin);
-    if (editWin) delwin(editWin);
-}
-
-
-BOOL nnwin_initialize_windows(void)
-{
-    int w, h;
-
-    getmaxyx(stdscr, h, w);
-
-    nnwin_close_windows();
-
-    mainWin = subwin(stdscr, h - 4, w, 0, 0);
-    statusWin = subwin(stdscr, 1, w, h - 4, 0);
-    editWin = subwin(stdscr, 3, w, h - 3, 0);
-
-    if (mainWin == NULL || statusWin == NULL || editWin == NULL)
-        return FALSE;
-
-    return TRUE;
-}
-
-
-void nnwin_update_all(void)
-{
-    if (mainWin) redrawwin(mainWin);
-    if (statusWin) redrawwin(statusWin);
-    if (editWin) redrawwin(editWin);
-}
-
-
 static void nn_tabcomplete_replace(nn_editbuf_t *buf, size_t *pi, const size_t startPos, const size_t endPos, char *c)
 {
     size_t i;
@@ -1578,31 +1271,6 @@
 }
 
 
-char *promptRequester(WINDOW *win, const char *info, BOOL allowEmpty)
-{
-    char tmpBuf[512], *ptr;
-    size_t pos;
-    int curVis = curs_set(1);
-
-    echo();
-    waddstr(win, info);
-    wgetnstr(win, tmpBuf, sizeof(tmpBuf) - 1);
-    noecho();
-    if (curVis != ERR)
-        curs_set(curVis);
-
-    for (pos = strlen(tmpBuf) - 1; pos > 0 && th_isspace(tmpBuf[pos]); pos--)
-        tmpBuf[pos] = 0;
-
-    ptr = str_trim_left(tmpBuf);
-
-    if (allowEmpty || strlen(ptr) > 0)
-        return th_strdup(ptr);
-    else
-        return NULL;
-}
-
-
 void printHelp(void)
 {
     printMsgQ(currWin, "\n"
@@ -1618,11 +1286,10 @@
 int main(int argc, char *argv[])
 {
     nn_conn_t *conn = NULL;
-    int curVis = ERR, updateCount = 0;
+    int updateCount = 0;
     BOOL argsOK, isError = FALSE,
         exitProg = FALSE,
         colorSet = FALSE,
-        cursesInit = FALSE,
         networkInit = FALSE,
         insertMode = TRUE,
         firstUpdate = TRUE;
@@ -1630,7 +1297,7 @@
     char *tmpStr;
     nn_editbuf_t *editBuf = nn_editbuf_new(NN_TMPBUF_SIZE);
     nn_editbuf_t *histBuf[SET_MAX_HISTORY+2];
-    int histPos = 0, histMax = 0;
+    int histPos = 0, histMax = 0, index;
 
     cfgitem_t *tmpcfg;
     char *setHomeDir = NULL;
@@ -1763,62 +1430,19 @@
     else
         networkInit = TRUE;
 
-    // Initialize NCurses
-    if (!optDaemon)
-    {
-        if (LINES < 0 || LINES > 1000) LINES = 24;
-        if (COLS < 0 || COLS > 1000) COLS = 80;
-        initscr();
-        raw();
-        keypad(stdscr, TRUE);
-        noecho();
-        meta(stdscr, TRUE);
-        timeout(SET_DELAY);
-        curVis = curs_set(0);
-
-        if (has_colors())
-        {
-            start_color();
+    // Initialize curses windowing
+    if (!optDaemon && !nnwin_init(SET_DELAY))
+        goto err_exit;
 
-            init_pair( 1, COLOR_RED,     COLOR_BLACK);
-            init_pair( 2, COLOR_GREEN,   COLOR_BLACK);
-            init_pair( 3, COLOR_YELLOW,  COLOR_BLACK);
-            init_pair( 4, COLOR_BLUE,    COLOR_BLACK);
-            init_pair( 5, COLOR_MAGENTA, COLOR_BLACK);
-            init_pair( 6, COLOR_CYAN,    COLOR_BLACK);
-            init_pair( 7, COLOR_WHITE,   COLOR_BLACK);
-            init_pair( 8, COLOR_BLACK,   COLOR_BLACK);
-
-            init_pair(10, COLOR_BLACK,   COLOR_RED);
-            init_pair(11, COLOR_WHITE,   COLOR_RED);
-            init_pair(12, COLOR_GREEN,   COLOR_RED);
-            init_pair(13, COLOR_YELLOW,  COLOR_RED);
-            init_pair(14, COLOR_BLUE,    COLOR_RED);
-            init_pair(15, COLOR_MAGENTA, COLOR_RED);
-            init_pair(16, COLOR_CYAN,    COLOR_RED);
-        }
-
-        cursesInit = TRUE;
-
-        if (!nnwin_initialize_windows())
-            goto err_exit;
-
-#ifdef PDCURSES
-        PDC_set_title("NNChat v" NN_VERSION);
-#endif
-
-        memset(chatWindows, 0, sizeof(chatWindows));
-        chatWindows[0] = nn_window_new(NULL);
-        currWin = chatWindows[0];
-        nnwin_update_statusline();
-    }
+    if (cursesInit)
+        nnwin_update_statusline(optUserName, optUserColor);
 
     // Check if we have username and password
     if (cursesInit && (optUserName == NULL || optPassword == NULL))
     {
         nnwin_print(editWin, "You can avoid this prompt by issuing '/save' after logging in.\n");
-        optUserName = promptRequester(editWin, "NN username: ", FALSE);
-        optPassword = promptRequester(editWin, "NN password: ", TRUE);
+        optUserName = nnwin_prompt_requester(editWin, "NN username: ", FALSE);
+        optPassword = nnwin_prompt_requester(editWin, "NN password: ", TRUE);
     }
 
     if (optUserName == NULL || optPassword == NULL)
@@ -1909,7 +1533,7 @@
 
         // First update of screen
         nnwin_update_editbuf(editBuf);
-        nnwin_update_statusline();
+        nnwin_update_statusline(optUserName, optUserColor);
 
         printMsg(NULL, "%s v%s - %s\n", th_prog_name, th_prog_version, th_prog_fullname);
         printMsg(NULL, "%s\n", th_prog_author);
@@ -2042,10 +1666,10 @@
                     if (c >= 0x31 && c <= 0x39)
                     {
                         // Chat window switching via Meta/Esc-[1..9]
-                        int win = c - 0x31;
-                        if (win < SET_MAX_WINDOWS && chatWindows[win] != NULL)
+                        nn_window_t *win = nnwin_get(c - 0x30);
+                        if (win != NULL)
                         {
-                            currWin = chatWindows[win];
+                            currWin = win;
                             update = updateMain = TRUE;
                         }
                         c = ERR;
@@ -2078,7 +1702,7 @@
                     erase();
                     timeout(SET_DELAY);
 
-                    if (!nnwin_initialize_windows())
+                    if (!nnwin_init_windows())
                     {
                         errorMsg("Error resizing curses chatWindows\n");
                         isError = TRUE;
@@ -2291,7 +1915,7 @@
             if (update || firstUpdate)
             {
                 // Update edit line
-                nnwin_update_statusline();
+                nnwin_update_statusline(optUserName, optUserColor);
                 nnwin_update_editbuf(editBuf);
                 firstUpdate = FALSE; // a nasty hack ...
             }
@@ -2315,7 +1939,7 @@
                 nn_conn_send_msg_v(conn, optUserNameEnc, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor);
             }
 
-            nnwin_update_statusline();
+            nnwin_update_statusline(optUserName, optUserColor);
             nnwin_update_editbuf(editBuf);
             updateCount = 0;
         }
@@ -2330,14 +1954,10 @@
     nn_userhash_free(nnUsers);
     nn_editbuf_free(editBuf);
 
-    {
-        int i;
-        for (i = 0; i <= SET_MAX_HISTORY; i++)
-            nn_editbuf_free(histBuf[i]);
+    for (index = 0; index <= SET_MAX_HISTORY; index++)
+        nn_editbuf_free(histBuf[index]);
 
-        for (i = 0; i < SET_MAX_WINDOWS; i++)
-            nn_window_free(chatWindows[i]);
-    }
+    nnwin_shutdown();
 
 #ifdef __WIN32
     if (errorMessages)
@@ -2349,14 +1969,6 @@
     }
 #endif
 
-    if (cursesInit)
-    {
-        if (curVis != ERR)
-            curs_set(curVis);
-        nnwin_close_windows();
-        endwin();
-        THMSG(1, "NCurses deinitialized.\n");
-    }
 
 #ifndef __WIN32
     if (errorMessages)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui.c	Sat May 26 06:56:18 2012 +0300
@@ -0,0 +1,461 @@
+/*
+ * NNChat - Custom chat client for NewbieNudes.com chatrooms
+ * Written by Matti 'ccr' Hämäläinen
+ * (C) Copyright 2008-2012 Tecnic Software productions (TNSP)
+ */
+#include "util.h"
+#include "ui.h"
+
+nn_window_t *chatWindows[SET_MAX_WINDOWS],
+            *currWin = NULL;
+
+BOOL    cursesInit = FALSE;
+int     cursorVisible = ERR;
+WINDOW  *mainWin = NULL,
+        *statusWin = NULL,
+        *editWin = NULL;
+
+
+static nn_window_t *nn_window_new(const char *id)
+{
+    nn_window_t *res = th_calloc(1, sizeof(nn_window_t));
+
+    if (res == NULL) return NULL;
+
+    res->data = th_ringbuf_new(NN_BACKBUF_LEN, th_free);
+    if (res->data == NULL)
+    {
+        th_free(res);
+        return NULL;
+    }
+
+    res->id = th_strdup(id);
+
+    return res;
+}
+
+
+static void nn_window_free(nn_window_t *win)
+{
+    if (win != NULL)
+    {
+        th_ringbuf_free(win->data);
+        th_free(win->id);
+        th_free(win);
+    }
+}
+
+
+nn_window_t *nnwin_main_window()
+{
+    return chatWindows[0];
+}
+
+
+nn_window_t *nnwin_get(const int index)
+{
+    if (index >= 1 && index <= SET_MAX_WINDOWS)
+        return chatWindows[index - 1];
+    else
+        return 0;
+    
+}
+
+
+BOOL nnwin_init(int delay)
+{
+    if (LINES < 0 || LINES > 1000) LINES = 24;
+    if (COLS < 0 || COLS > 1000) COLS = 80;
+
+    initscr();
+    raw();
+    keypad(stdscr, TRUE);
+    noecho();
+    meta(stdscr, TRUE);
+    timeout(delay);
+    cursorVisible = curs_set(0);
+
+    if (has_colors())
+    {
+        start_color();
+
+        init_pair( 1, COLOR_RED,     COLOR_BLACK);
+        init_pair( 2, COLOR_GREEN,   COLOR_BLACK);
+        init_pair( 3, COLOR_YELLOW,  COLOR_BLACK);
+        init_pair( 4, COLOR_BLUE,    COLOR_BLACK);
+        init_pair( 5, COLOR_MAGENTA, COLOR_BLACK);
+        init_pair( 6, COLOR_CYAN,    COLOR_BLACK);
+        init_pair( 7, COLOR_WHITE,   COLOR_BLACK);
+        init_pair( 8, COLOR_BLACK,   COLOR_BLACK);
+
+        init_pair(10, COLOR_BLACK,   COLOR_RED);
+        init_pair(11, COLOR_WHITE,   COLOR_RED);
+        init_pair(12, COLOR_GREEN,   COLOR_RED);
+        init_pair(13, COLOR_YELLOW,  COLOR_RED);
+        init_pair(14, COLOR_BLUE,    COLOR_RED);
+        init_pair(15, COLOR_MAGENTA, COLOR_RED);
+        init_pair(16, COLOR_CYAN,    COLOR_RED);
+    }
+
+    cursesInit = TRUE;
+
+    if (!nnwin_init_windows())
+        return FALSE;
+
+#ifdef PDCURSES
+        PDC_set_title("NNChat v" NN_VERSION);
+#endif
+
+    memset(chatWindows, 0, sizeof(chatWindows));
+    chatWindows[0] = nn_window_new(NULL);
+    currWin = chatWindows[0];
+    
+    return TRUE;
+}
+
+
+void nnwin_shutdown()
+{
+    int i;
+
+    for (i = 0; i < SET_MAX_WINDOWS; i++)
+        nn_window_free(chatWindows[i]);
+
+    if (cursesInit)
+    {
+        if (cursorVisible != ERR)
+            curs_set(cursorVisible);
+
+        nnwin_close_windows();
+
+        endwin();
+        THMSG(1, "NCurses deinitialized.\n");
+    }
+}
+
+
+nn_window_t *nnwin_find(const char *id)
+{
+    int i;
+
+    for (i = 0; i < SET_MAX_WINDOWS; i++)
+    {
+        if (chatWindows[i] != NULL &&
+            chatWindows[i]->id != NULL &&
+            th_strcasecmp(id, chatWindows[i]->id) == 0)
+            return chatWindows[i];
+    }
+
+    return NULL;
+}
+
+
+BOOL nnwin_open(const char *name, BOOL curwin)
+{
+    int i;
+    nn_window_t *res;
+    if (name == NULL)
+        return FALSE;
+
+    if ((res = nn_window_new(name)) == NULL)
+        return FALSE;
+
+    for (i = 1; i < SET_MAX_WINDOWS; i++)
+        if (chatWindows[i] == NULL)
+        {
+            res->num = i;
+            chatWindows[i] = res;
+            if (curwin)
+                currWin = res;
+            return TRUE;
+        }
+
+    return FALSE;
+}
+
+
+void nnwin_close(nn_window_t *win)
+{
+    int i;
+    if (win == NULL) return;
+
+    for (i = 1; i < SET_MAX_WINDOWS; i++)
+        if (chatWindows[i] == win)
+        {
+            chatWindows[i] = NULL;
+            nn_window_free(win);
+            return;
+        }
+}
+
+
+void nnwin_update_statusline(char *optUserName, int optUserColor)
+{
+    char tmpStr[128];
+    int i;
+
+    if (statusWin == NULL) return;
+
+    str_get_timestamp(tmpStr, sizeof(tmpStr), "%H:%M:%S");
+
+    wbkgdset(statusWin, COLOR_PAIR(10));
+    werase(statusWin);
+
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
+    mvwaddstr(statusWin, 0, 1, tmpStr);
+
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
+    waddstr(statusWin, " | ");
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(16));
+    waddstr(statusWin, optUserName);
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
+
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
+    waddstr(statusWin, " | ");
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
+    snprintf(tmpStr, sizeof(tmpStr), "#%06x", optUserColor);
+    waddstr(statusWin, tmpStr);
+
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
+    waddstr(statusWin, " | WIN: ");
+    snprintf(tmpStr, sizeof(tmpStr), "%d: %s / %d",
+        currWin->num + 1,
+        currWin->id != NULL ? currWin->id : "MAIN",
+        currWin->pos);
+    waddstr(statusWin, tmpStr);
+
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(13));
+    waddstr(statusWin, " | ");
+    wattrset(statusWin, A_BOLD | COLOR_PAIR(11));
+
+    for (i = 0; i < SET_MAX_WINDOWS; i++)
+    {
+        if (chatWindows[i] != NULL && chatWindows[i]->dirty)
+        {
+            snprintf(tmpStr, sizeof(tmpStr), "%d ", i + 1);
+            waddstr(statusWin, tmpStr);
+        }
+    }
+
+    wrefresh(statusWin);
+}
+
+
+void nnwin_update_editbuf(nn_editbuf_t *buf)
+{
+    char *tmp;
+    if (editWin == NULL || buf == NULL) return;
+
+    buf->data[buf->len] = 0;
+    tmp = nn_username_decode(th_strdup(buf->data));
+
+    werase(editWin);
+
+    wattrset(editWin, A_NORMAL);
+
+    if (buf->pos < buf->len)
+    {
+        waddnstr(editWin, tmp, buf->pos);
+        wattrset(editWin, A_REVERSE);
+        waddch(editWin, tmp[buf->pos]);
+        wattrset(editWin, A_NORMAL);
+        waddnstr(editWin, tmp + buf->pos + 1, buf->len - buf->pos - 1);
+    }
+    else
+    {
+        waddnstr(editWin, tmp, buf->len);
+        wattrset(editWin, A_REVERSE);
+        waddch(editWin, ' ');
+        wattrset(editWin, A_NORMAL);
+    }
+    wrefresh(editWin);
+    th_free(tmp);
+}
+
+
+int nnwin_print(WINDOW *win, const char *fmt)
+{
+    const char *s = fmt;
+    int col = 0;
+
+    while (*s)
+    {
+        if (*s == '½')
+        {
+            s++;
+            if (*s == '½')
+            {
+                waddch(win, ((unsigned char) *s) | col);
+                s++;
+            }
+            else
+            {
+                memcpy(&col, s, sizeof(int));
+                s += sizeof(int);
+            }
+        }
+        else
+        {
+            waddch(win, ((unsigned char) *s) | col);
+            s++;
+        }
+    }
+    return 0;
+}
+
+
+#define QPUTCH(ch) th_vputch(&(win->buf), &(win->bufsize), &(win->len), ch)
+
+int nnwin_print_buf(nn_window_t *win, const char *fmt)
+{
+    const char *s = fmt;
+    int col = 0;
+    while (*s)
+    {
+        if (*s == '½')
+        {
+            s++;
+            if (*s == '½')
+            {
+                QPUTCH(*s);
+                QPUTCH(*s);
+                win->chlen++;
+            }
+            else
+            {
+                int val = 0;
+                while (*s >= '0' && *s <= '9')
+                {
+                    val *= 10;
+                    val += (*s - '0');
+                    s++;
+                }
+                if (*s != '½') return -1;
+
+                if (val < 9)
+                    col = A_DIM | COLOR_PAIR(val);
+                else if (val < 30)
+                    col = A_BOLD | COLOR_PAIR(val - 9);
+
+                QPUTCH('½');
+
+                if (!th_growbuf(&(win->buf), &(win->bufsize), &(win->len), sizeof(int)))
+                    return -2;
+
+                memcpy(win->buf + win->len, &col, sizeof(int));
+                win->len += sizeof(int);
+            }
+        }
+        else if (*s == '\n')
+        {
+            QPUTCH('\n');
+            QPUTCH(0);
+            th_ringbuf_add(win->data, win->buf);
+            win->buf = NULL;
+            win->chlen = 0;
+            win->dirty = TRUE;
+        }
+        else if (*s != '\r')
+        {
+            QPUTCH((unsigned char) *s == 255 ? ' ' : *s);
+            win->chlen++;
+        }
+
+        s++;
+    }
+
+    return 0;
+}
+
+
+void nnwin_update_all(void)
+{
+    if (mainWin) redrawwin(mainWin);
+    if (statusWin) redrawwin(statusWin);
+    if (editWin) redrawwin(editWin);
+}
+
+
+char *nnwin_prompt_requester(WINDOW *win, const char *info, BOOL allowEmpty)
+{
+    char tmpBuf[512], *ptr;
+    size_t pos;
+    int curSave = curs_set(1);
+
+    echo();
+    waddstr(win, info);
+    wgetnstr(win, tmpBuf, sizeof(tmpBuf) - 1);
+    noecho();
+
+    if (curSave != ERR)
+        curs_set(curSave);
+
+    for (pos = strlen(tmpBuf) - 1; pos > 0 && th_isspace(tmpBuf[pos]); pos--)
+        tmpBuf[pos] = 0;
+
+    ptr = str_trim_left(tmpBuf);
+
+    if (allowEmpty || strlen(ptr) > 0)
+        return th_strdup(ptr);
+    else
+        return NULL;
+}
+
+
+BOOL nnwin_update_main(BOOL force)
+{
+    int h, offs;
+    qringbuf_t *buf;
+
+    // Check pointers
+    if (mainWin == NULL || currWin == NULL)
+        return FALSE;
+
+    // Check if update is forced or if the window is dirty
+    if (!force && !currWin->dirty)
+        return FALSE;
+
+    // Compute how many lines from backbuffer fit on the screen
+    buf = currWin->data;
+    h = getmaxy(mainWin);
+
+    // Clear and redraw window
+    werase(mainWin);
+    scrollok(mainWin, 1);
+    for (offs = buf->size - h - currWin->pos; offs >= 0 && offs < buf->size - currWin->pos && offs < buf->size; offs++)
+    {
+        if (buf->data[offs] != NULL)
+            nnwin_print(mainWin, buf->data[offs]);
+    }
+
+    currWin->dirty = FALSE;
+    wrefresh(mainWin);
+    return TRUE;
+}
+
+
+void nnwin_close_windows(void)
+{
+    if (mainWin) delwin(mainWin);
+    if (statusWin) delwin(statusWin);
+    if (editWin) delwin(editWin);
+}
+
+
+BOOL nnwin_init_windows(void)
+{
+    int w, h;
+
+    getmaxyx(stdscr, h, w);
+
+    nnwin_close_windows();
+
+    mainWin = subwin(stdscr, h - 4, w, 0, 0);
+    statusWin = subwin(stdscr, 1, w, h - 4, 0);
+    editWin = subwin(stdscr, 3, w, h - 3, 0);
+
+    if (mainWin == NULL || statusWin == NULL || editWin == NULL)
+        return FALSE;
+
+    return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui.h	Sat May 26 06:56:18 2012 +0300
@@ -0,0 +1,63 @@
+/*
+ * NNChat - Custom chat client for NewbieNudes.com chatrooms
+ * Written by Matti 'ccr' Hämäläinen
+ * (C) Copyright 2008-2012 Tecnic Software productions (TNSP)
+ */
+#ifndef LIBNNUI_H
+#define LIBNNUI_H
+
+#include "th_types.h"
+#include "th_string.h"
+
+#ifdef HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+
+#define SET_MAX_WINDOWS (32)
+#define NN_BACKBUF_LEN    (512)       // Backbuffer size (in lines)
+
+typedef struct
+{
+    qringbuf_t *data;   // "Backbuffer" data for this window
+    int pos;            // Current position in the window, 0 = real time
+    BOOL dirty;
+
+    char *id;           // Chatter ID, NULL = main window
+    int num;		// Window number
+
+    char *buf;
+    size_t len, bufsize;
+    size_t chlen;
+} nn_window_t;
+
+
+extern nn_window_t *currWin;
+extern BOOL cursesInit;
+extern WINDOW *mainWin, *statusWin, *editWin;
+
+BOOL nnwin_init(int delay);
+void nnwin_shutdown();
+
+BOOL nnwin_init_windows(void);
+void nnwin_close_windows(void);
+
+nn_window_t *nnwin_main_window();
+nn_window_t *nnwin_get(const int index);
+nn_window_t *nnwin_find(const char *id);
+
+BOOL     nnwin_open(const char *name, BOOL curwin);
+void     nnwin_close(nn_window_t *win);
+
+void     nnwin_update_statusline(char *optUserName, int optUserColor);
+void     nnwin_update_editbuf(nn_editbuf_t *buf);
+int      nnwin_print(WINDOW *win, const char *fmt);
+int      nnwin_print_buf(nn_window_t *win, const char *fmt);
+void     nnwin_update_all(void);
+char *   nnwin_prompt_requester(WINDOW *win, const char *info, BOOL allowEmpty);
+BOOL     nnwin_update_main(BOOL force);
+
+
+#endif
--- a/util.c	Sat May 26 06:18:19 2012 +0300
+++ b/util.c	Sat May 26 06:56:18 2012 +0300
@@ -683,36 +683,6 @@
 }
 
 
-nn_window_t *nn_window_new(const char *id)
-{
-    nn_window_t *res = th_calloc(1, sizeof(nn_window_t));
-
-    if (res == NULL) return NULL;
-
-    res->data = th_ringbuf_new(NN_BACKBUF_LEN, th_free);
-    if (res->data == NULL)
-    {
-        th_free(res);
-        return NULL;
-    }
-
-    res->id = th_strdup(id);
-
-    return res;
-}
-
-
-void nn_window_free(nn_window_t *win)
-{
-    if (win != NULL)
-    {
-        th_ringbuf_free(win->data);
-        th_free(win->id);
-        th_free(win);
-    }
-}
-
-
 nn_strtuple_t *nn_strtuple_new(size_t len, char *str)
 {
     nn_strtuple_t *tuple = th_calloc(1, sizeof(nn_strtuple_t));
--- a/util.h	Sat May 26 06:18:19 2012 +0300
+++ b/util.h	Sat May 26 06:56:18 2012 +0300
@@ -15,7 +15,6 @@
 #define NN_TMPBUF_SIZE    (4096)
 #define NN_ALLOC_SIZE     (128)
 #define NN_NUM_BUCKETS    (256)
-#define NN_BACKBUF_LEN    (512)       // Backbuffer size (in lines)
 
 
 typedef struct _nn_user_t
@@ -72,24 +71,6 @@
 
 typedef struct
 {
-    qringbuf_t *data;   // "Backbuffer" data for this window
-    int pos;            // Current position in the window, 0 = real time
-    BOOL dirty;
-
-    char *id;           // Chatter ID, NULL = main window
-    int num;		// Window number
-
-    char *buf;
-    size_t len, bufsize;
-    size_t chlen;
-} nn_window_t;
-
-nn_window_t *nn_window_new(const char *);
-void        nn_window_free(nn_window_t *);
-
-
-typedef struct
-{
     size_t len;
     char *str;
 } nn_strtuple_t;