view ui.c @ 610:224b28e82698

Bump version.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 20 May 2014 04:49:17 +0300
parents 199fd3371035
children 2c6945599b16
line wrap: on
line source

/*
 * NNChat - Custom chat client for NewbieNudes.com chatrooms
 * Written by Matti 'ccr' Hämäläinen
 * (C) Copyright 2008-2013 Tecnic Software productions (TNSP)
 */
#include "util.h"
#include "ui.h"


nn_window_t *chatWindows[SET_MAX_WINDOWS],
            *currWin = NULL;

BOOL    appCursesInit = FALSE, appQuitFlag = FALSE;
int     cursorVisible = ERR,
        scrWidth, scrHeight;


static void nn_line_free(void *ptr)
{
    nn_line_t *line = (nn_line_t *) ptr;
    if (line != NULL)
    {
        th_free(line->buf);
        th_free(line);
    }
}


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, nn_line_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 NULL;
}


BOOL nnwin_init(int delay)
{
    // Sanity check the terminal size
    if (LINES < 0 || LINES > 1000) LINES = 24;
    if (COLS < 0 || COLS > 1000) COLS = 80;

    // Initialize (n)curses library and terminal settings
    initscr();
    raw();
    keypad(stdscr, TRUE);
    noecho();
    meta(stdscr, TRUE);
    timeout(delay);
    scrollok(stdscr, FALSE);
    cursorVisible = curs_set(1);

    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);
    }

    appCursesInit = TRUE;
    nnwin_reset();

#ifdef PDCURSES
    PDC_set_title("NNChat v" NN_VERSION);
#endif

    memset(chatWindows, 0, sizeof(chatWindows));
    chatWindows[0] = nn_window_new(NULL);
    nn_log_open(chatWindows[0]);
    currWin = chatWindows[0];
    
    return TRUE;
}


void nnwin_shutdown()
{
    int i;

    for (i = 0; i < SET_MAX_WINDOWS; i++)
        nn_window_free(chatWindows[i]);

    if (appCursesInit)
    {
        if (cursorVisible != ERR)
            curs_set(cursorVisible);

        endwin();
        THMSG(1, "NCurses deinitialized.\n");
    }
}


void nnwin_reset(void)
{
    getmaxyx(stdscr, scrHeight, scrWidth);
}


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)
{
    nn_window_t *res;
    int i;

    if (name == NULL)
        return FALSE;

    if ((res = nn_window_new(name)) == NULL)
        return FALSE;

    nn_log_open(res);

    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_log_close(win);
        nn_window_free(win);
        return;
    }
}


static BOOL nnwin_get_color(char const **s, int *col)
{
    int val = 0;

    while (**s >= '0' && **s <= '9')
    {
        val *= 10;
        val += (**s - '0');
        (*s)++;
    }
    if (**s != '½')
        return FALSE;

    if (val < 9)
        *col = A_DIM | COLOR_PAIR(val);
    else if (val < 30)
        *col = A_BOLD | COLOR_PAIR(val - 9);
    
    return TRUE;
}


#define QPUTCH(ch) nnwin_putch(&(win->line->buf), &(win->line->bufsize), &(win->line->len), col, ch)

static BOOL nnwin_putch(int **buf, size_t *bufsize, size_t *len, int color, char ch)
{
    if (*buf == NULL || *len + 1 >= *bufsize)
    {
        *bufsize += TH_BUFGROW;
        *buf = (int *) th_realloc(*buf, *bufsize * sizeof(int));
        if (*buf == NULL)
            return FALSE;
    }

    (*buf)[*len] = ((unsigned char) ch) | color;
    (*len)++;
    
    return TRUE;
}


int nnwin_print(nn_window_t *win, const char *fmt)
{
    const char *s = fmt;
    int col = 0;
    
    if (win == NULL)
        return -16;
    
    while (*s)
    {
        if (win->line == NULL)
        {
            win->line = th_calloc(1, sizeof(nn_line_t));
            if (win->line == NULL)
                return -15;
        }

        if (*s == '½')
        {
            s++;
            if (*s == '½')
            {
                QPUTCH(*s);
            }
            else
            {
                if (!nnwin_get_color(&s, &col))
                    return -1;
            }
        }
        else if (*s == '\n')
        {
            th_ringbuf_add(win->data, win->line);
            win->line = NULL;
            win->dirty = TRUE;
        }
        else if (*s != '\r')
        {
            QPUTCH((unsigned char) *s == 255 ? ' ' : *s);
        }

        s++;
    }

    return 0;
}


static void nnwin_print_str(WINDOW *win, const char *fmt, BOOL clip)
{
    const char *s = fmt;
    int col = 0;
    while (*s)
    {
        if (clip && getcurx(win) >= scrWidth)
            return;

        if (*s == '½')
        {
            s++;
            if (*s == '½')
            {
                waddch(win, ((unsigned char) *s) | col);
                s++;
            }
            else
            {
                if (!nnwin_get_color(&s, &col))
                    return;
            }
        }
        else
        {
            waddch(win, ((unsigned char) *s) | col);
            s++;
        }
    }
}


void nnwin_update(BOOL force, BOOL mask, nn_editbuf_t *ebuf, char *username, int usercolor)
{
    int sx, sy;
    BOOL changed = FALSE;

    // Clear screen if forced or main or editbuf are dirty
    if (force || (currWin != NULL && currWin->dirty) || (ebuf != NULL && ebuf->dirty))
    {
        // Save cursor position
        getyx(stdscr, sy, sx);

        wattrset(stdscr, A_NORMAL);
        wbkgdset(stdscr, COLOR_PAIR(0));
        werase(stdscr);
        force = TRUE;
        changed = TRUE;
    }
    else
    {
        // Save cursor position
        getyx(stdscr, sy, sx);
    }
    
    // Check if update is forced or if the window is dirty
    if (currWin != NULL && (force || currWin->dirty))
    {
        int y, offs;
        qringbuf_t *buf = currWin->data;

        changed = TRUE;

        for (y = scrHeight - 4, offs = buf->size - 1 - currWin->pos; offs >= 0 && y > 0; offs--)
        {
            nn_line_t *line = (nn_line_t *) buf->data[offs];
            if (line != NULL)
            {
                const int *s = line->buf;
                size_t pos;

                y -= (line->len / scrWidth);
                if (line->len % scrWidth != 0)
                    y -= 1;

                if (y < 0)
                {
                    size_t r;
                    int x;
                    for (r = -y, x = 0, pos = 0; r && pos < line->len; pos++)
                    {
                        if (++x >= scrWidth)
                        {
                            x = 0;
                            r++;
                        }
                    }
                    y = 0;
                }

                wmove(stdscr, y, 0);

                for (pos = 0; pos < line->len; pos++)
                    waddch(stdscr, s[pos]);
            }
        }

        currWin->dirty = FALSE;
    }

    // Update statusline
    if (changed || force)
    {
        char tmpStamp[32], tmpStr[128];
        int i;

        str_get_timestamp(tmpStamp, sizeof(tmpStamp), "%H:%M:%S");

#if 0
        snprintf(tmpStr, sizeof(tmpStr),
            " ½10½%s½13½ | ½16½%s½13½ | ½11½#%06x½13½ | WIN: %d: %s / %d | ½11½",
            tmpStamp,
            username,
            usercolor, 
            currWin->num + 1,
            currWin->id != NULL ? currWin->id : "MAIN",
            currWin->pos);
#else
        snprintf(tmpStr, sizeof(tmpStr),
            " %s | %s | #%06x | WIN: %d: %s / %d | ",
            tmpStamp,
            username != NULL ? username : "-",
            usercolor,
            currWin != NULL ? currWin->num + 1 : 0,
            (currWin != NULL && currWin->id != NULL) ? currWin->id : "MAIN",
            currWin != NULL ? currWin->pos : 0);
#endif
        
        wmove(stdscr, scrHeight - 4, 0);
        wbkgdset(stdscr, COLOR_PAIR(10));
        wclrtoeol(stdscr);
        nnwin_print_str(stdscr, tmpStr, TRUE);

        for (i = 0; i < SET_MAX_WINDOWS; i++)
        {
            if (chatWindows[i] != NULL && chatWindows[i]->dirty)
            {
                snprintf(tmpStr, sizeof(tmpStr), "%d ", i + 1);
                waddstr(stdscr, tmpStr);
            }
        }
    
        // Restore cursor position
        wmove(stdscr, sy, sx);
    }

    // Update editbuf if needed
    if (ebuf != NULL && (force || ebuf->dirty))
    {
        int yoffs = ebuf->pos / scrWidth,
            xoffs = ebuf->pos % scrWidth;

        wmove(stdscr, scrHeight - 3, 0);
        wattrset(stdscr, A_NORMAL);
        wbkgdset(stdscr, COLOR_PAIR(0));

        ebuf->dirty = FALSE;
        if (mask)
        {
            size_t i;
            for (i = 0; i < ebuf->len; i++)
                waddch(stdscr, '*');
        }
        else
        {
            char *tmp;
            ebuf->data[ebuf->len] = 0;
            tmp = nn_username_decode(th_strdup(ebuf->data));
            waddnstr(stdscr, tmp, ebuf->len);
            th_free(tmp);
        }
        wmove(stdscr, scrHeight - 3 + yoffs, xoffs);

        changed = TRUE;
    }

    if (changed)
        wrefresh(stdscr);
}


void nnwin_input_process(nn_editbuf_t *editBuf, nn_editstate_t *editState,
    BOOL (*callback)(int, nn_editbuf_t *, nn_editstate_t *))
{
    int c, cnt = 0;
    
    // Handle several buffered keypresses at once
    do
    {
        c = wgetch(stdscr);
        
        /* Handle various problematic cases where terminal 
         * keycodes do not get properly translated by curses
         */
        if (c == 10 || c == 13)
            c = KEY_ENTER;
        else
        if (c == 0x1b)
        {
            // ^[O
            c = wgetch(stdscr);
            if (c == 'O')
            {
                c = wgetch(stdscr);
                switch (c)
                {
                case 'd':
                    c = 0x204;
                    break;
                case 'c':
                    c = 0x206;
                    break;
                default:
                    editState->debugMsg("Unhandled ESC-O key sequence 0x%02x\n", c);
                    break;
                }
            }
            // ^[[
            else if (c == '[')
            {
                c = wgetch(stdscr);
                switch (c)
                {
                case 0x31:
                    c = wgetch(stdscr);
                    if (c >= 0x31 && c <= 0x39)
                        c = KEY_F(c - 0x30);
                    else
                        c = ERR;
                    break;

                case 0x32:
                    c = KEY_IC;
                    break;
                case 0x33:
                    c = KEY_DC;
                    break;

                case 0x35:
                    c = KEY_PPAGE;
                    break;
                case 0x36:
                    c = KEY_NPAGE;
                    break;

                case 0x37:
                    c = KEY_HOME;
                    break;
                case 0x38:
                    c = KEY_END;
                    break;

                default:
                    editState->debugMsg("Unhandled ESC-[*~ key sequence 0x%02x\n", c);
                    c = ERR;
                    break;
                }
                // Get the trailing ~
                if (c != ERR)
                    wgetch(stdscr);
            }
            else
            if (c >= 0x31 && c <= 0x39)
            {
                c = c - 0x30 + 0x5000;
            }
            else
            {
                editState->debugMsg("Unhandled ESC key sequence 0x%02x\n", c);
            }
        }
#if defined(__WIN32) && defined(PDCURSES)
        else
        if (c >= 0x198 && c <= 0x1a0)
        {
            c = c - 0x198 + 0x5000;
        }
#endif

        switch (c)
        {
#ifdef KEY_RESIZE
        case KEY_RESIZE:
            resize_term(0, 0);
            erase();
            timeout(SET_DELAY);
            nnwin_reset();
            editState->update = TRUE;
            break;
#endif

        case 0x204: // ctrl+left arrow = Skip words left
        case 0x20b:
            while (editBuf->pos > 0 && isspace((int) editBuf->data[editBuf->pos - 1]))
                editBuf->pos--;
            while (editBuf->pos > 0 && !isspace((int) editBuf->data[editBuf->pos - 1]))
                editBuf->pos--;
            editBuf->dirty = TRUE;
            break;

        case 0x206: // ctrl+right arrow = Skip words right
        case 0x210:
            while (editBuf->pos < editBuf->len && isspace((int) editBuf->data[editBuf->pos]))
                editBuf->pos++;
            while (editBuf->pos < editBuf->len && !isspace((int) editBuf->data[editBuf->pos]))
                editBuf->pos++;
            editBuf->dirty = TRUE;
            break;

        case KEY_HOME:
            nn_editbuf_setpos(editBuf, 0);
            break;
        case KEY_END:
            nn_editbuf_setpos(editBuf, editBuf->len);
            break;
        case KEY_LEFT:
            nn_editbuf_setpos(editBuf, editBuf->pos - 1);
            break;
        case KEY_RIGHT:
            nn_editbuf_setpos(editBuf, editBuf->pos + 1);
            break;

        case KEY_BACKSPACE:
        case 0x08:
        case 0x7f:
            if (editBuf->pos > 0)
            {
                nn_editbuf_delete(editBuf, editBuf->pos - 1);
                nn_editbuf_setpos(editBuf, editBuf->pos - 1);
            }
            break;

        case KEY_DC: // Delete character
            nn_editbuf_delete(editBuf, editBuf->pos);
            break;

        case KEY_IC: // Ins = Toggle insert / overwrite mode
            editState->insertMode = !editState->insertMode;
            break;

        case KEY_F(2): // F2 = Clear editbuffer
            nn_editbuf_clear(editBuf);
            break;

        case 0x0c: // Ctrl + L
            editState->update = TRUE;
            break;

        case ERR:
            // Ignore
            break;

        default:
            if (!callback(c, editBuf, editState))
            {
                if (isprint(c) || c == 0xe4 || c == 0xf6 || c == 0xc4 || c == 0xd6)
                {
                    if (editState->insertMode)
                        nn_editbuf_insert(editBuf, editBuf->pos, c);
                    else
                        nn_editbuf_write(editBuf, editBuf->pos, c);
                    nn_editbuf_setpos(editBuf, editBuf->pos + 1);
                }
                else
                    editState->debugMsg("Unhandled key: 0x%02x\n", c);
            }
            break;
        }
    }
    while (c != ERR && ++cnt < 10);
}



char *nnwin_prompt_requester(BOOL allowEmpty, nn_editstate_t *editState,
    BOOL (*callback)(int, nn_editbuf_t *, nn_editstate_t *),
    void (*update)(nn_editbuf_t *, nn_editstate_t *))
{
    nn_editbuf_t *editBuf = nn_editbuf_new(NN_TMPBUF_SIZE);
    char *res;

    editState->done = FALSE;
    while (!editState->isError && !appQuitFlag && !editState->done)
    {
        nnwin_input_process(editBuf, editState, callback);
        update(editBuf, editState);
    }    
    
    if (allowEmpty || editBuf->len > 0)
        res = nn_editbuf_get_string(editBuf, 0, editBuf->len);
    else
        res = NULL;

    nn_editbuf_free(editBuf);
    
    return res;
}