view mcabber/src/screen.c @ 147:7571de4aed73

[/trunk] Changeset 159 by mikael * Fix a bug in buddylist_build() * We now lock the current buddy even not when being in chat mode. For example, if we're writing to s.o. and he leaves just before we press enter, we won't write to the wrong buddy... If the current_buddy is a group, we lock it too. * Remove MCABBER_TESTUNIT ifdef in roster.h (test program isn't up-to-date anymore...)
author mikael
date Fri, 29 Apr 2005 19:56:28 +0000
parents 300bb88f631f
children c3624b2a7059
line wrap: on
line source

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ncurses.h>
#include <panel.h>
#include <time.h>
#include <ctype.h>
#include <locale.h>

#include "screen.h"
#include "hbuf.h"
#include "commands.h"
#include "compl.h"
#include "roster.h"
#include "parsecfg.h"
#include "utils.h"
#include "list.h"

#define window_entry(n) list_entry(n, window_entry_t, list)

LIST_HEAD(window_list);

typedef struct _window_entry_t {
  WINDOW *win;
  PANEL  *panel;
  char   *name;
  GList  *hbuf;
  GList  *top; // If top is not specified (NULL), we'll display the last lines
  char    cleared; // For ex, user has issued a /clear command...
  struct list_head list;
} window_entry_t;


static WINDOW *rosterWnd, *chatWnd, *inputWnd;
static WINDOW *logWnd, *logWnd_border;
static PANEL *rosterPanel, *chatPanel, *inputPanel;
static PANEL *logPanel, *logPanel_border;
static int maxY, maxX;
static window_entry_t *currentWindow;

static int chatmode;
int update_roster;

static char inputLine[INPUTLINE_LENGTH+1];
static char *ptr_inputline;
static short int inputline_offset;
static int  completion_started;


/* Functions */

int scr_WindowWidth(WINDOW * win)
{
  int x, y;
  getmaxyx(win, y, x);
  return x;
}

void scr_clear_box(WINDOW *win, int y, int x, int height, int width, int Color)
{
  int i, j;

  wattrset(win, COLOR_PAIR(Color));
  for (i = 0; i < height; i++) {
    wmove(win, y + i, x);
    for (j = 0; j < width; j++)
      wprintw(win, " ");
  }
}

void scr_draw_box(WINDOW * win, int y, int x, int height, int width,
                  int Color, chtype box, chtype border)
{
  int i, j;

  wattrset(win, COLOR_PAIR(Color));
  for (i = 0; i < height; i++) {
    wmove(win, y + i, x);
    for (j = 0; j < width; j++)
      if (!i && !j)
	waddch(win, border | ACS_ULCORNER);
      else if (i == height - 1 && !j)
	waddch(win, border | ACS_LLCORNER);
      else if (!i && j == width - 1)
	waddch(win, box | ACS_URCORNER);
      else if (i == height - 1 && j == width - 1)
	waddch(win, box | ACS_LRCORNER);
      else if (!i)
	waddch(win, border | ACS_HLINE);
      else if (i == height - 1)
	waddch(win, box | ACS_HLINE);
      else if (!j)
	waddch(win, border | ACS_VLINE);
      else if (j == width - 1)
	waddch(win, box | ACS_VLINE);
      else
	waddch(win, box | ' ');
  }
}

int FindColor(char *name)
{
  if (!strcmp(name, "default"))
    return -1;
  if (!strcmp(name, "black"))
    return COLOR_BLACK;
  if (!strcmp(name, "red"))
    return COLOR_RED;
  if (!strcmp(name, "green"))
    return COLOR_GREEN;
  if (!strcmp(name, "yellow"))
    return COLOR_YELLOW;
  if (!strcmp(name, "blue"))
    return COLOR_BLUE;
  if (!strcmp(name, "magenta"))
    return COLOR_MAGENTA;
  if (!strcmp(name, "cyan"))
    return COLOR_CYAN;
  if (!strcmp(name, "white"))
    return COLOR_WHITE;

  return -1;
}

void ParseColors(void)
{
  char *colors[11] = {
    "", "",
    "borderlines",
    "jidonline",
    "newmsg",
    "jidofflineselected",
    "jidoffline",
    "text",
    NULL
  };

  char *tmp = malloc(1024);
  char *color1;
  char *background = cfg_read("color_background");
  char *backselected = cfg_read("color_backselected");
  int i = 0;

  while (colors[i]) {
    sprintf(tmp, "color_%s", colors[i]);
    color1 = cfg_read(tmp);

    switch (i + 1) {
    case 1:
      init_pair(1, COLOR_BLACK, COLOR_WHITE);
      break;
    case 2:
      init_pair(2, COLOR_WHITE, COLOR_BLACK);
      break;
    case 3:
      init_pair(3, FindColor(color1), FindColor(background));
      break;
    case 4:
      init_pair(4, FindColor(color1), FindColor(backselected));
      break;
    case 5:
      init_pair(5, FindColor(color1), FindColor(background));
      break;
    case 6:
      init_pair(6, FindColor(color1), FindColor(backselected));
      break;
    case 7:
      init_pair(7, FindColor(color1), FindColor(background));
      break;
    case 8:
      init_pair(8, FindColor(color1), FindColor(background));
      break;
    }
    i++;
  }
}


window_entry_t *scr_CreatePanel(const char *title, int x, int y,
                                int lines, int cols, int dont_show)
{
  window_entry_t *tmp = calloc(1, sizeof(window_entry_t));

  tmp->win = newwin(lines, cols, y, x);
  tmp->panel = new_panel(tmp->win);
  tmp->name = (char *) calloc(1, 1024);
  strncpy(tmp->name, title, 1024);
  scr_clear_box(tmp->win, 0, 0, lines, cols, COLOR_GENERAL);

  if (!dont_show) {
    currentWindow = tmp;
  } else {
    if (currentWindow)
      top_panel(currentWindow->panel);
    else
      top_panel(chatPanel);
  }
  update_panels();

  list_add_tail(&tmp->list, &window_list);

  return tmp;
}

window_entry_t *scr_SearchWindow(const char *winId)
{
  struct list_head *pos, *n;
  window_entry_t *search_entry = NULL;

  list_for_each_safe(pos, n, &window_list) {
    search_entry = window_entry(pos);
    if (search_entry->name) {
      if (!strcasecmp(search_entry->name, winId)) {
	return search_entry;
      }
    }
  }
  return NULL;
}

//  scr_UpdateWindow()
// (Re-)Display the given chat window.
void scr_UpdateWindow(window_entry_t *win_entry)
{
  int n;
  int width;
  char **lines;
  GList *hbuf_head;

  width = scr_WindowWidth(win_entry->win);

  // Should the window be empty?
  if (win_entry->cleared) {
    scr_clear_box(win_entry->win, 0, 0, CHAT_WIN_HEIGHT, width, COLOR_GENERAL);
    return;
  }

  // win_entry->top is the top message of the screen.  If it set to NULL, we
  // are displaying the last messages.

  // We will show the last CHAT_WIN_HEIGHT lines.
  // Let's find out where it begins.
  if (!win_entry->top ||
      (g_list_position(g_list_first(win_entry->hbuf), win_entry->top) == -1)) {
    // Move up CHAT_WIN_HEIGHT lines
    win_entry->hbuf = g_list_last(win_entry->hbuf);
    hbuf_head = win_entry->hbuf;
    win_entry->top = NULL; // (Just to make sure)
    n = 0;
    while (hbuf_head && (n < CHAT_WIN_HEIGHT-1) && g_list_previous(hbuf_head)) {
      hbuf_head = g_list_previous(hbuf_head);
      n++;
    }
  } else
    hbuf_head = win_entry->top;

  // Get the last CHAT_WIN_HEIGHT lines.
  lines = hbuf_get_lines(hbuf_head, CHAT_WIN_HEIGHT);

  // Display these lines
  wmove(win_entry->win, 0, 0);
  for (n = 0; n < CHAT_WIN_HEIGHT; n++) {
    int r = width;
    if (*(lines+2*n)) {
      if (**(lines+2*n))
        wprintw(win_entry->win, "%s", *(lines+2*n));      // prefix
      else {
        wprintw(win_entry->win, "            ");
        r -= 12;
      }
      wprintw(win_entry->win, "%s", *(lines+2*n+1));      // line
      // Calculate the number of blank characters to empty the line
      r -= strlen(*(lines+2*n)) + strlen(*(lines+2*n+1));
    }
    for ( ; r>0 ; r--) {
      wprintw(win_entry->win, " ");
    }
    //// wclrtoeol(win_entry->win);  does not work :(
  }
  g_free(lines);
}

//  scr_ShowWindow()
// Display the chat window with the given identifier.
void scr_ShowWindow(const char *winId)
{
  window_entry_t *win_entry = scr_SearchWindow(winId);

  if (win_entry != NULL) {
    top_panel(win_entry->panel);
    currentWindow = win_entry;
    chatmode = TRUE;
    roster_setflags(winId, ROSTER_FLAG_MSG, FALSE);
    roster_setflags(winId, ROSTER_FLAG_LOCK, TRUE);
    update_roster = TRUE;

    // Refresh the window
    scr_UpdateWindow(win_entry);

    // Finished :)
    update_panels();
    doupdate();
  } else {
    top_panel(chatPanel);
    currentWindow = win_entry;  // == NULL  (current window empty)
  }

  top_panel(inputPanel);
}

//  scr_ShowBuddyWindow()
// Display the chat window buffer for the current buddy.
void scr_ShowBuddyWindow(void)
{
  const gchar *jid;

  if (!current_buddy)
    jid = NULL;
  else
    jid = CURRENT_JID;

  if (!jid) {
    top_panel(chatPanel);
    top_panel(inputPanel);
    currentWindow = NULL;
    return;
  }

  scr_ShowWindow(jid);
}


//  scr_WriteInWindow()
// Write some text in the winId window (this usually is a jid).
// Lines are splitted when they are too long to fit in the chat window.
// If this window doesn't exist, it is created.
void scr_WriteInWindow(const char *winId, const char *text, int TimeStamp,
        const char *prefix, int force_show)
{
  char *fullprefix = NULL;
  window_entry_t *win_entry;
  int dont_show = FALSE;

  // Prepare the prefix
  if (prefix || TimeStamp) {
    if (!prefix)  prefix = "";
    fullprefix = calloc(1, strlen(prefix)+16);
    if (TimeStamp) {
      time_t now = time(NULL);
      strftime(fullprefix, 12, "[%H:%M] ", localtime(&now));
    } else {
      strcpy(fullprefix, "            ");
    }
    strcat(fullprefix, prefix);
  }

  // Look for the window entry.
  win_entry = scr_SearchWindow(winId);

  // Do we have to really show the window?
  if (!chatmode)
    dont_show = TRUE;
  else if ((!force_show) && ((!currentWindow || (currentWindow != win_entry))))
    dont_show = TRUE;

  // If the window entry doesn't exist yet, let's create it.
  if (win_entry == NULL) {
    win_entry = scr_CreatePanel(winId, ROSTER_WIDTH, 0, CHAT_WIN_HEIGHT,
                          maxX - ROSTER_WIDTH, dont_show);
  }

  hbuf_add_line(&win_entry->hbuf, text, fullprefix,
                maxX - scr_WindowWidth(rosterWnd) - 14);
  free(fullprefix);

  if (win_entry->cleared) {
    win_entry->cleared = 0; // The message must be displayed
    win_entry->top = g_list_last(win_entry->hbuf);
  }

  if (!dont_show) {
    // Show and refresh the window
    top_panel(win_entry->panel);
    scr_UpdateWindow(win_entry);
    top_panel(inputPanel);
    update_panels();
    doupdate();
  } else {
    roster_setflags(winId, ROSTER_FLAG_MSG, TRUE);
    update_roster = TRUE;
  }
}

void scr_InitCurses(void)
{
  initscr();
  noecho();
  raw();
  halfdelay(5);
  start_color();
  use_default_colors();

  ParseColors();

  getmaxyx(stdscr, maxY, maxX);
  inputLine[0] = 0;
  ptr_inputline = inputLine;

  setlocale(LC_CTYPE, "");

  return;
}

void scr_TerminateCurses(void)
{
  clear();
  refresh();
  endwin();
  return;
}

void scr_DrawMainWindow(void)
{
  int l;

  /* Draw main panels */
  rosterWnd = newwin(CHAT_WIN_HEIGHT, ROSTER_WIDTH, 0, 0);
  rosterPanel = new_panel(rosterWnd);
  scr_clear_box(rosterWnd, 0, 0, CHAT_WIN_HEIGHT, ROSTER_WIDTH,
                COLOR_GENERAL);
  for (l=0 ; l < CHAT_WIN_HEIGHT ; l++)
    mvwaddch(rosterWnd, l, ROSTER_WIDTH-1, ACS_VLINE);

  chatWnd = newwin(CHAT_WIN_HEIGHT, maxX - ROSTER_WIDTH, 0, ROSTER_WIDTH);
  chatPanel = new_panel(chatWnd);
  scr_clear_box(chatWnd, 0, 0, CHAT_WIN_HEIGHT, maxX - ROSTER_WIDTH,
                COLOR_GENERAL);
  scrollok(chatWnd, TRUE);
  mvwprintw(chatWnd, 0, 0, "This is the status window");

  logWnd_border = newwin(LOG_WIN_HEIGHT, maxX, CHAT_WIN_HEIGHT, 0);
  logPanel_border = new_panel(logWnd_border);
  scr_draw_box(logWnd_border, 0, 0, LOG_WIN_HEIGHT, maxX, COLOR_GENERAL, 0, 0);
  logWnd = derwin(logWnd_border, LOG_WIN_HEIGHT-2, maxX-2, 1, 1);
  logPanel = new_panel(logWnd);
  wbkgd(logWnd, COLOR_PAIR(COLOR_GENERAL));

  scrollok(logWnd, TRUE);

  inputWnd = newwin(1, maxX, maxY-1, 0);
  inputPanel = new_panel(inputWnd);

  scr_DrawRoster();
  return;
}

//  scr_DrawRoster()
// Actually, display the buddylist on the screen.
void scr_DrawRoster(void)
{
  static guint offset = 0;
  char name[ROSTER_WIDTH];
  int maxx, maxy;
  GList *buddy;
  int i, n;
  int rOffset;

  // We can reset update_roster
  update_roster = FALSE;

  getmaxyx(rosterWnd, maxy, maxx);
  maxx --;  // last char is for vertical border
  name[ROSTER_WIDTH-7] = 0;

  // cleanup of roster window
  wattrset(rosterWnd, COLOR_PAIR(COLOR_GENERAL));
  for (i = 0; i < maxy; i++) {
    mvwprintw(rosterWnd, i, 0, "");
    for (n = 0; n < maxx; n++)
      waddch(rosterWnd, ' ');
  }

  // Leave now if buddylist is empty
  if (!buddylist) {
    offset = 0;
    update_panels();
    doupdate();
    return;
  }

  // Update offset if necessary
  i = g_list_position(buddylist, current_buddy);
  if (i == -1) { // This is bad
    scr_LogPrint("Doh! Can't find current selected buddy!!");
    update_panels();
    doupdate();
    return;
  } else if (i < offset) {
    offset = i;
  } else if (i+1 > offset + maxy) {
    offset = i + 1 - maxy;
  }

  buddy = buddylist;
  rOffset = offset;

  for (i=0; i<maxy && buddy; buddy = g_list_next(buddy)) {

    char status = '?';
    char pending = ' ';
    enum imstatus budstate;

    if (rOffset > 0) {
      rOffset--;
      continue;
    }

    if (buddy_getflags(BUDDATA(buddy)) & ROSTER_FLAG_MSG) {
      pending = '#';
    }

    budstate = buddy_getstatus(BUDDATA(buddy));
    if (budstate >= 0 && budstate < imstatus_size)
      status = imstatus2char[budstate];
    if (buddy == current_buddy) {
      wattrset(rosterWnd, COLOR_PAIR(COLOR_BD_DESSEL));
      // The 3 following lines aim to color the whole line
      wmove(rosterWnd, i, 0);
      for (n = 0; n < maxx; n++)
        waddch(rosterWnd, ' ');
    } else {
      if (buddy_getflags(BUDDATA(buddy)) & ROSTER_FLAG_MSG)
        wattrset(rosterWnd, COLOR_PAIR(COLOR_NMSG));
      else
        wattrset(rosterWnd, COLOR_PAIR(COLOR_BD_DES));
    }

    strncpy(name, buddy_getname(BUDDATA(buddy)), ROSTER_WIDTH-7);
    if (buddy_gettype(BUDDATA(buddy)) & ROSTER_TYPE_GROUP) {
      char *sep;
      if (buddy_getflags(BUDDATA(buddy)) & ROSTER_FLAG_HIDE)
        sep = "+++";
      else
        sep = "---";
      mvwprintw(rosterWnd, i, 0, " %c%s %s", pending, sep, name);
    }
    else
      mvwprintw(rosterWnd, i, 0, " %c[%c] %s", pending, status, name);

    i++;
  }

  top_panel(inputPanel);
  update_panels();
  doupdate();
}

void scr_WriteMessage(const char *jid, const char *text, char *prefix)
{
  scr_WriteInWindow(jid, text, TRUE, prefix, FALSE);
}

void scr_WriteIncomingMessage(const char *jidfrom, const char *text)
{
  // FIXME expand tabs / filter out special chars...
  scr_WriteMessage(jidfrom, text, "<== ");
  update_panels();
  doupdate();
}

void scr_WriteOutgoingMessage(const char *jidto, const char *text)
{
  scr_WriteMessage(jidto, text, "--> ");
  scr_ShowWindow(jidto);
}

int scr_Getch(void)
{
  int ch;
  ch = wgetch(inputWnd);
  return ch;
}

WINDOW *scr_GetRosterWindow(void)
{
  return rosterWnd;
}

WINDOW *scr_GetStatusWindow(void)
{
  return chatWnd;
}

WINDOW *scr_GetInputWindow(void)
{
  return inputWnd;
}

//  scr_RosterTop()
// Go to the first buddy in the buddylist
void scr_RosterTop(void)
{
  enum imstatus prev_st = imstatus_size; // undef

  if (current_buddy) {
    prev_st = buddy_getstatus(BUDDATA(current_buddy));
    if (chatmode)
      buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
  }
  current_buddy = buddylist;
  if (chatmode && current_buddy)
    buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, TRUE);

  // We should rebuild the buddylist but not everytime
  // Here we check if we were locking a buddy who is actually offline,
  // and hide_offline_buddies is TRUE.  In which case we need to rebuild.
  if (current_buddy && prev_st == offline &&
          buddylist_get_hide_offline_buddies())
    buddylist_build();
  if (chatmode)
    scr_ShowBuddyWindow();
  update_roster = TRUE;
}

//  scr_RosterBottom()
// Go to the last buddy in the buddylist
void scr_RosterBottom(void)
{
  enum imstatus prev_st = imstatus_size; // undef

  if (current_buddy) {
    prev_st = buddy_getstatus(BUDDATA(current_buddy));
    if (chatmode)
      buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
  }
  current_buddy = g_list_last(buddylist);
  // Lock the buddy in the buddylist if we're in chat mode
  if (chatmode && current_buddy)
    buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, TRUE);

  // We should rebuild the buddylist but not everytime
  // Here we check if we were locking a buddy who is actually offline,
  // and hide_offline_buddies is TRUE.  In which case we need to rebuild.
  if (current_buddy && prev_st == offline &&
          buddylist_get_hide_offline_buddies())
    buddylist_build();

  if (chatmode)
    scr_ShowBuddyWindow();
  update_roster = TRUE;
}

//  scr_RosterUp()
// Go to the previous buddy in the buddylist
void scr_RosterUp(void)
{
  enum imstatus prev_st = imstatus_size; // undef

  if (current_buddy) {
    if (g_list_previous(current_buddy)) {
      prev_st = buddy_getstatus(BUDDATA(current_buddy));
      buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
      current_buddy = g_list_previous(current_buddy);
      // Lock the buddy in the buddylist if we're in chat mode
      if (chatmode)
        buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, TRUE);
      // We should rebuild the buddylist but not everytime
      // Here we check if we were locking a buddy who is actually offline,
      // and hide_offline_buddies is TRUE.  In which case we need to rebuild.
      if (prev_st == offline && buddylist_get_hide_offline_buddies())
        buddylist_build();
      update_roster = TRUE;
    }
  }

  if (chatmode)
    scr_ShowBuddyWindow();
}

//  scr_RosterDown()
// Go to the next buddy in the buddylist
void scr_RosterDown(void)
{
  enum imstatus prev_st = imstatus_size; // undef

  if (current_buddy) {
    if (g_list_next(current_buddy)) {
      prev_st = buddy_getstatus(BUDDATA(current_buddy));
      buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
      current_buddy = g_list_next(current_buddy);
      if (chatmode)
        buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, TRUE);
      // We should rebuild the buddylist but not everytime
      // Here we check if we were locking a buddy who is actually offline,
      // and hide_offline_buddies is TRUE.  In which case we need to rebuild.
      if (prev_st == offline && buddylist_get_hide_offline_buddies())
        buddylist_build();
      update_roster = TRUE;
    }
  }

  if (chatmode)
    scr_ShowBuddyWindow();
}

//  scr_ScrollUp()
// Scroll up the current buddy window, half a screen.
void scr_ScrollUp(void)
{
  const gchar *jid;
  window_entry_t *win_entry;
  int n, nblines;
  GList *hbuf_top;

  // Get win_entry
  if (!current_buddy)
    return;
  jid = CURRENT_JID;
  if (!jid)
    return;
  win_entry  = scr_SearchWindow(jid);
  if (!win_entry)
    return;

  // Scroll up half a screen (or less)
  nblines = CHAT_WIN_HEIGHT/2-1;
  hbuf_top = win_entry->top;
  if (!hbuf_top) {
    hbuf_top = g_list_last(win_entry->hbuf);
    if (!win_entry->cleared)
      nblines *= 3;
    else
      win_entry->cleared = FALSE;
  }

  n = 0;
  while (hbuf_top && n < nblines && g_list_previous(hbuf_top)) {
    hbuf_top = g_list_previous(hbuf_top);
    n++;
  }
  win_entry->top = hbuf_top;

  // Refresh the window
  scr_UpdateWindow(win_entry);

  // Finished :)
  update_panels();
  doupdate();
}

//  scr_ScrollDown()
// Scroll down the current buddy window, half a screen.
void scr_ScrollDown(void)
{
  const gchar *jid;
  window_entry_t *win_entry;
  int n, nblines;
  GList *hbuf_top;

  // Get win_entry
  if (!current_buddy)
    return;
  jid = CURRENT_JID;
  if (!jid)
    return;
  win_entry  = scr_SearchWindow(jid);
  if (!win_entry)
    return;

  // Scroll down half a screen (or less)
  nblines = CHAT_WIN_HEIGHT/2-1;
  hbuf_top = win_entry->top;

  for (n=0 ; hbuf_top && n < nblines ; n++)
    hbuf_top = g_list_next(hbuf_top);
  win_entry->top = hbuf_top;
  // Check if we are at the bottom
  for (n=0 ; hbuf_top && n < CHAT_WIN_HEIGHT-1 ; n++)
    hbuf_top = g_list_next(hbuf_top);
  if (!hbuf_top)
    win_entry->top = NULL; // End reached

  // Refresh the window
  scr_UpdateWindow(win_entry);

  // Finished :)
  update_panels();
  doupdate();
}

//  scr_Clear()
// Clear the current buddy window (used for the /clear command)
void scr_Clear(void)
{
  const gchar *jid;
  window_entry_t *win_entry;

  // Get win_entry
  if (!current_buddy)
    return;
  jid = CURRENT_JID;
  if (!jid)
    return;
  win_entry  = scr_SearchWindow(jid);
  if (!win_entry)
    return;

  win_entry->cleared = TRUE;
  win_entry->top = NULL;

  // Refresh the window
  scr_UpdateWindow(win_entry);

  // Finished :)
  update_panels();
  doupdate();
}

//  scr_LogPrint(...)
// Display a message in the log window.
void scr_LogPrint(const char *fmt, ...)
{
  time_t timestamp;
  char *buffer;
  va_list ap;

  buffer = (char *) calloc(1, 4096);

  timestamp = time(NULL);
  strftime(buffer, 64, "[%H:%M:%S] ", localtime(&timestamp));
  wprintw(logWnd, "\n%s", buffer);

  va_start(ap, fmt);
  vsnprintf(buffer, 4096, fmt, ap);
  va_end(ap);

  wprintw(logWnd, "%s", buffer);
  free(buffer);

  update_panels();
  doupdate();
}

//  scr_set_chatmode()
// Public fonction to (un)set chatmode...
inline void scr_set_chatmode(int enable)
{
  chatmode = enable;
}

//  which_row()
// Tells which row our cursor is in, in the command line.
// -1 -> normal text
//  0 -> command
//  1 -> parameter 1 (etc.)
//  If > 0, then *p_row is set to the beginning of the row
int which_row(char **p_row)
{
  int row = -1;
  char *p;
  int quote = FALSE;

  // Not a command?
  if ((ptr_inputline == inputLine) || (inputLine[0] != '/'))
    return -1;

  // This is a command
  row = 0;
  for (p = inputLine ; p < ptr_inputline ; p++) {
    if (quote) {
      if (*p == '"' && *(p-1) != '\\')
        quote = FALSE;
      continue;
    }
    if (*p == '"' && *(p-1) != '\\') {
      quote = TRUE;
    } else if (*p == ' ') {
      if (*(p-1) != ' ')
        row++;
      *p_row = p+1;
    }
  }
  return row;
}

//  scr_insert_text()
// Insert the given text at the current cursor position.
// The cursor is moved.  We don't check if the cursor still is in the screen
// after, the caller should do that.
void scr_insert_text(const char *text)
{
  char tmpLine[INPUTLINE_LENGTH+1];
  int len = strlen(text);
  // Check the line isn't too long
  if (strlen(inputLine) + len >= INPUTLINE_LENGTH) {
    scr_LogPrint("Cannot insert text, line too long.");
    return;
  }

  strcpy(tmpLine, ptr_inputline);
  strcpy(ptr_inputline, text);    ptr_inputline += len;
  strcpy(ptr_inputline, tmpLine);
}

//  scr_handle_tab()
// Function called when tab is pressed.
// Initiate or continue a completion...
void scr_handle_tab(void)
{
  int nrow;
  char *row;
  const char *cchar;
  guint compl_categ;

  nrow = which_row(&row);

  // a) No completion if no leading slash ('cause not a command)
  // b) We can't have more than 2 parameters (we use 2 flags)
  if (nrow < 0 || nrow > 2) return;

  if (nrow == 0) {      // Command completion
    row = &inputLine[1];
    compl_categ = COMPL_CMD;
  } else {              // Other completion, depending on the command
    cmd *com = cmd_get(inputLine);
    if (!com || !row) {
      scr_LogPrint("I cannot complete that...");
      return;
    }
    compl_categ = com->completion_flags[nrow-1];
  }

  if (!completion_started) {
    GSList *list = compl_get_category_list(compl_categ);
    if (list) {
      char *prefix = g_strndup(row, ptr_inputline-row);
      // Init completion
      new_completion(prefix, list);
      g_free(prefix);
      // Now complete
      cchar = complete();
      if (cchar)
        scr_insert_text(cchar);
      completion_started = TRUE;
    }
  } else {      // Completion already initialized
    char *c;
    guint back = cancel_completion();
    // Remove $back chars
    ptr_inputline -= back;
    c = ptr_inputline;
    for ( ; *c ; c++)
      *c = *(c+back);
    // Now complete again
    cchar = complete();
    if (cchar)
      scr_insert_text(cchar);
  }
}

void scr_cancel_current_completion(void)
{
  char *c;
  guint back = cancel_completion();
  // Remove $back chars
  ptr_inputline -= back;
  c = ptr_inputline;
  for ( ; *c ; c++)
    *c = *(c+back);
}

void scr_end_current_completion(void)
{
  done_completion();
  completion_started = FALSE;
}

//  check_offset(int direction)
// Check inputline_offset value, and make sure the cursor is inside the
// screen.
inline void check_offset(int direction)
{
  // Left side
  if (inputline_offset && direction <= 0) {
    while (ptr_inputline <= (char*)&inputLine + inputline_offset) {
      if (inputline_offset) {
        inputline_offset -= 5;
        if (inputline_offset < 0)
          inputline_offset = 0;
      }
    }
  }
  // Right side
  if (direction >= 0) {
    while (ptr_inputline >= inputline_offset + (char*)&inputLine + maxX)
      inputline_offset += 5;
  }
}

//  process_key(key)
// Handle the pressed key, in the command line (bottom).
int process_key(int key)
{
  if (isprint(key)) {
    char tmpLine[INPUTLINE_LENGTH+1];

    // Check the line isn't too long
    if (strlen(inputLine) >= INPUTLINE_LENGTH)
      return 0;

    // Insert char
    strcpy(tmpLine, ptr_inputline);
    *ptr_inputline++ = key;
    strcpy(ptr_inputline, tmpLine);
    check_offset(1);
  } else {
    switch(key) {
      case KEY_BACKSPACE:
          if (ptr_inputline != (char*)&inputLine) {
            char *c = --ptr_inputline;
            for ( ; *c ; c++)
              *c = *(c+1);
            check_offset(-1);
          }
          break;
      case KEY_DC:
          if (*ptr_inputline)
            strcpy(ptr_inputline, ptr_inputline+1);
          break;
      case KEY_LEFT:
          if (ptr_inputline != (char*)&inputLine) {
            ptr_inputline--;
            check_offset(-1);
          }
          break;
      case KEY_RIGHT:
          if (*ptr_inputline)
            ptr_inputline++;
            check_offset(1);
          break;
      case 7:     // Ctrl-g
          scr_cancel_current_completion();
          scr_end_current_completion();
          check_offset(-1);
          break;
      case 9:     // Tab
          scr_handle_tab();
          check_offset(0);
          break;
      case '\n':  // Enter
          if (process_line(inputLine))
            return 255;
          ptr_inputline = inputLine;
          *ptr_inputline = 0;
          inputline_offset = 0;
          break;
      case KEY_UP:
          scr_RosterUp();
          break;
      case KEY_DOWN:
          scr_RosterDown();
          break;
      case KEY_PPAGE:
          scr_ScrollUp();
          break;
      case KEY_NPAGE:
          scr_ScrollDown();
          break;
      case KEY_HOME:
      case 1:
          ptr_inputline = inputLine;
          inputline_offset = 0;
          break;
      case KEY_END:
      case 5:
          for (; *ptr_inputline; ptr_inputline++) ;
          check_offset(1);
          break;
      case 21:  // Ctrl-u
          strcpy(inputLine, ptr_inputline);
          ptr_inputline = inputLine;
          inputline_offset = 0;
          break;
      case KEY_EOL:
      case 11:  // Ctrl-k
          *ptr_inputline = 0;
          break;
      case 16:  // Ctrl-p
          scr_LogPrint("Ctrl-p not yet implemented");
          break;
      case 14:  // Ctrl-n
          scr_LogPrint("Ctrl-n not yet implemented");
          break;
      case 27:  // ESC
          currentWindow = NULL;
          chatmode = FALSE;
          if (current_buddy)
            buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
          top_panel(chatPanel);
          top_panel(inputPanel);
          break;
      default:
          scr_LogPrint("Unkown key=%d", key);
    }
  }
  if (completion_started && key != 9)
    scr_end_current_completion();
  mvwprintw(inputWnd, 0,0, "%s", inputLine + inputline_offset);
  wclrtoeol(inputWnd);
  if (*ptr_inputline) {
    wmove(inputWnd, 0, ptr_inputline - (char*)&inputLine - inputline_offset);
  }
  update_panels();
  doupdate();
  return 0;
}