/*
 * screen.c     -- UI stuff
 *
 * Copyright (C) 2005-2014 Mikael Berthe <mikael@lilotux.net>
 * Parts of this file come from the Cabber project <cabber@ajmacias.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>

#include <config.h>
#include <locale.h>
#include <assert.h>
#ifdef USE_SIGWINCH
# include <sys/ioctl.h>
# include <termios.h>
# include <unistd.h>
#endif

#ifdef HAVE_LOCALCHARSET_H
# include <localcharset.h>
#else
# include <langinfo.h>
#endif

#ifdef WITH_ENCHANT
# include <enchant.h>
#else
# ifdef WITH_ASPELL
#  include <aspell.h>
# endif
#endif

#include "screen.h"
#include "utf8.h"
#include "hbuf.h"
#include "commands.h"
#include "compl.h"
#include "roster.h"
#include "histolog.h"
#include "settings.h"
#include "utils.h"
#include "xmpp.h"
#include "main.h"

#define get_color(col)      (COLOR_PAIR(col)|COLOR_ATTRIB[col])
#define compose_color(col)  (COLOR_PAIR(col->color_pair)|col->color_attrib)

#define DEFAULT_LOG_WIN_HEIGHT (5+2)
#define DEFAULT_ROSTER_WIDTH    24
#define CHAT_WIN_HEIGHT (maxY-1-Log_Win_Height)

#define DEFAULT_ATTENTION_CHAR '!'

const char *LocaleCharSet = "C";

static unsigned short int Log_Win_Height;
static unsigned short int Roster_Width;
static gboolean colors_stalled = FALSE;

// Default attention sign trigger levels
static guint ui_attn_sign_prio_level_muc = ROSTER_UI_PRIO_MUC_HL_MESSAGE;
static guint ui_attn_sign_prio_level     = ROSTER_UI_PRIO_ATTENTION_MESSAGE;

static inline void check_offset(int);
static void scr_cancel_current_completion(void);
static void scr_end_current_completion(void);
static void scr_insert_text(const char*);
static void scr_handle_tab(gboolean fwd);

static void scr_glog_print(const gchar *log_domain, GLogLevelFlags log_level,
                           const gchar *message, gpointer user_data);

#ifdef XEP0085
static gboolean scr_chatstates_timeout();
#endif

#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
static void spellcheck(char *, char *);
#endif

static GHashTable *winbufhash;

typedef struct {
  GList    *hbuf;
  GList    *top;      // If top is NULL, we'll display the last lines
  char      cleared;  // For ex, user has issued a /clear command...
  char      lock;
  char      refcount; // refcount > 0 if there are other users of this struct
                      // e.g. with symlinked history
} buffdata;

typedef struct {
  WINDOW *win;
  PANEL  *panel;
  buffdata *bd;
} winbuf;

struct dimensions {
  int l;
  int c;
};

static WINDOW *rosterWnd, *chatWnd, *activechatWnd, *inputWnd, *logWnd;
static WINDOW *mainstatusWnd, *chatstatusWnd;
static PANEL *rosterPanel, *chatPanel, *activechatPanel, *inputPanel;
static PANEL *mainstatusPanel, *chatstatusPanel;
static PANEL *logPanel;
static int maxY, maxX;
static int prev_chatwidth;
static winbuf *statusWindow;
static winbuf *currentWindow;
static GList  *statushbuf;

static int roster_hidden;
static int chatmode;
static int multimode;
static char *multiline, *multimode_subj;
static int roster_no_leading_space;

static bool Curses;
static bool log_win_on_top;
static bool roster_win_on_right;
static guint autoaway_source = 0;

static char       inputLine[INPUTLINE_LENGTH+1];
#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
static char       maskLine[INPUTLINE_LENGTH+1];
#endif
static char      *ptr_inputline;
static short int  inputline_offset;
static int    completion_started;
static GList *cmdhisto;
static GList *cmdhisto_cur;
static guint  cmdhisto_nblines;
static char   cmdhisto_backup[INPUTLINE_LENGTH+1];

static int    chatstate; /* (0=active, 1=composing, 2=paused) */
static bool   lock_chatstate;
static time_t chatstate_timestamp;
static guint  chatstate_timeout_id = 0;

int _update_roster;
int utf8_mode;
gboolean chatstates_disabled;
gboolean Autoaway;

#define MAX_KEYSEQ_LENGTH 8

typedef struct {
  char *seqstr;
  guint mkeycode;
  gint  value;
} keyseq;

GSList *keyseqlist;
static void add_keyseq(char *seqstr, guint mkeycode, gint value);

static void scr_write_in_window(const char *winId, const char *text,
                                time_t timestamp, unsigned int prefix_flags,
                                int force_show, unsigned mucnicklen,
                                gpointer xep184);

static void scr_write_message(const char *bjid, const char *text,
                              time_t timestamp, guint prefix_flags,
                              unsigned mucnicklen, gpointer xep184);

inline void scr_update_buddy_window(void);
inline void scr_set_chatmode(int enable);

#define SPELLBADCHAR 5

#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
typedef struct {
#ifdef WITH_ENCHANT
  EnchantBroker *broker;
  EnchantDict *checker;
#endif
#ifdef WITH_ASPELL
  AspellConfig *config;
  AspellSpeller *checker;
#endif
} spell_checker;

GSList* spell_checkers = NULL;
#endif

typedef struct {
  int color_pair;
  int color_attrib;
} ccolor;

typedef struct {
  char *status, *wildcard;
  ccolor *color;
  GPatternSpec *compiled;
} rostercolor;

static GSList *rostercolrules = NULL;

static GHashTable *muccolors = NULL, *nickcolors = NULL;

typedef struct {
  bool manual; // Manually set?
  ccolor *color;
} nickcolor;

static int nickcolcount = 0;
static ccolor ** nickcols = NULL;
static muccoltype glob_muccol = MC_OFF;

/* Functions */

static int find_color(const char *name)
{
  int result;

  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;

  // Directly support 256-color values
  result = atoi(name);
  if (result > 0 && (result < COLORS || !Curses))
    return result;

  scr_LogPrint(LPRINT_LOGNORM, "ERROR: Wrong color: %s", name);
  return -1;
}

static ccolor *get_user_color(const char *color)
{
  bool isbright = FALSE;
  int cl;
  ccolor *ccol;
  if (!strncmp(color, "bright", 6)) {
    isbright = TRUE;
    color += 6;
  }
  cl = find_color(color);
  if (cl < 0)
    return NULL;
  ccol = g_new0(ccolor, 1);
  ccol->color_attrib = isbright ? A_BOLD : A_NORMAL;
  ccol->color_pair = cl + COLOR_max; // User colors come after the internal ones
  return ccol;
}

static void ensure_string_htable(GHashTable **table,
                                 GDestroyNotify value_destroy_func)
{
  if (*table) // Have it already
    return;
  *table = g_hash_table_new_full(g_str_hash, g_str_equal,
      g_free, value_destroy_func);
}

// Sets the coloring mode for given MUC
// The MUC room does not need to be in the roster at that time
// muc - the JID of room
// type - the new type
void scr_muc_color(const char *muc, muccoltype type)
{
  gchar *muclow = g_utf8_strdown(muc, -1);
  if (type == MC_REMOVE) { // Remove it
    if (strcmp(muc, "*")) {
      if (muccolors && g_hash_table_lookup(muccolors, muclow))
        g_hash_table_remove(muccolors, muclow);
    } else {
      scr_LogPrint(LPRINT_NORMAL, "Can not remove global coloring mode");
    }
    g_free(muclow);
  } else { // Add or overwrite
    if (strcmp(muc, "*")) {
      muccoltype *value = g_new(muccoltype, 1);
      *value = type;
      ensure_string_htable(&muccolors, g_free);
      g_hash_table_replace(muccolors, muclow, value);
    } else {
      glob_muccol = type;
      g_free(muclow);
    }
  }
  // Need to redraw?
  if (chatmode &&
      ((buddy_search_jid(muc) == current_buddy) || !strcmp(muc, "*")))
    scr_update_buddy_window();
}

// Sets the color for nick in MUC
// If color is "-", the color is marked as automaticly assigned and is
// not used if the room is in the "preset" mode
void scr_muc_nick_color(const char *nick, const char *color)
{
  char *snick, *mnick;
  bool need_update = FALSE;
  snick = g_strdup_printf("<%s>", nick);
  mnick = g_strdup_printf("*%s ", nick);
  if (!strcmp(color, "-")) { // Remove the color
    if (nickcolors) {
      nickcolor *nc = g_hash_table_lookup(nickcolors, snick);
      if (nc) { // Have this nick already
        nc->manual = FALSE;
        nc = g_hash_table_lookup(nickcolors, mnick);
        assert(nc); // Must have both at the same time
        nc->manual = FALSE;
      }// Else -> no color saved, nothing to delete
    }
    g_free(snick); // They are not saved in the hash
    g_free(mnick);
    need_update = TRUE;
  } else {
    ccolor *cl = get_user_color(color);
    if (!cl) {
      scr_LogPrint(LPRINT_NORMAL, "No such color name");
      g_free(snick);
      g_free(mnick);
    } else {
      nickcolor *nc = g_new(nickcolor, 1);
      ensure_string_htable(&nickcolors, NULL);
      nc->manual = TRUE;
      nc->color = cl;
      // Free the struct, if any there already
      g_free(g_hash_table_lookup(nickcolors, mnick));
      // Save the new ones
      g_hash_table_replace(nickcolors, mnick, nc);
      g_hash_table_replace(nickcolors, snick, nc);
      need_update = TRUE;
    }
  }
  if (need_update && chatmode &&
      (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_ROOM))
    scr_update_buddy_window();
}

static void free_rostercolrule(rostercolor *col)
{
  g_free(col->status);
  g_free(col->wildcard);
  g_free(col->color);
  g_pattern_spec_free(col->compiled);
  g_free(col);
}

// Removes all roster coloring rules
void scr_roster_clear_color(void)
{
  GSList *head;
  for (head = rostercolrules; head; head = g_slist_next(head)) {
    free_rostercolrule(head->data);
  }
  g_slist_free(rostercolrules);
  rostercolrules = NULL;
  scr_update_roster();
}

// Adds, modifies or removes roster coloring rule
// color set to "-" removes the rule,
// otherwise it is modified (if exists) or added
//
// Returns weather it was successfull (therefore the roster should be
// redrawed) or not. If it failed, for example because of invalid color
// name, it also prints the error.
bool scr_roster_color(const char *status, const char *wildcard,
                      const char *color)
{
  GSList *head;
  GSList *found = NULL;
  for (head = rostercolrules; head; head = g_slist_next(head)) {
    rostercolor *rc = head->data;
    if ((!strcmp(status, rc->status)) && (!strcmp(wildcard, rc->wildcard))) {
      found = head;
      break;
    }
  }
  if (!strcmp(color,"-")) { // Delete the rule
    if (found) {
      free_rostercolrule(found->data);
      rostercolrules = g_slist_delete_link(rostercolrules, found);
      scr_update_roster();
      return TRUE;
    } else {
      scr_LogPrint(LPRINT_NORMAL, "No such color rule, nothing removed");
      return FALSE;
    }
  } else {
    ccolor *cl = get_user_color(color);
    if (!cl) {
      scr_LogPrint(LPRINT_NORMAL, "No such color name");
      return FALSE;
    }
    if (found) {
      rostercolor *rc = found->data;
      g_free(rc->color);
      rc->color = cl;
    } else {
      rostercolor *rc = g_new(rostercolor, 1);
      rc->status = g_strdup(status);
      rc->wildcard = g_strdup(wildcard);
      rc->compiled = g_pattern_spec_new(wildcard);
      rc->color = cl;
      rostercolrules = g_slist_prepend(rostercolrules, rc);
    }
    scr_update_roster();
    return TRUE;
  }
}

static void parse_colors(void)
{
  const char *colors[] = {
    "", "",
    "general",
    "msgout",
    "msghl",
    "status",
    "log",
    "roster",
    "rostersel",
    "rosterselmsg",
    "rosternewmsg",
    "info",
    "msgin",
    "readmark",
    "timestamp",
    NULL
  };

  const char *color;
  const char *background   = settings_opt_get("color_background");
  const char *backselected = settings_opt_get("color_bgrostersel");
  const char *backstatus   = settings_opt_get("color_bgstatus");
  char *tmp;
  int i;

  // Initialize color attributes
  memset(COLOR_ATTRIB, 0, sizeof(COLOR_ATTRIB));

  // Default values
  if (!background)   background   = "black";
  if (!backselected) backselected = "cyan";
  if (!backstatus)   backstatus   = "blue";

  for (i=0; colors[i]; i++) {
    tmp = g_strdup_printf("color_%s", colors[i]);
    color = settings_opt_get(tmp);
    g_free(tmp);

    if (color) {
      if (!strncmp(color, "bright", 6)) {
        COLOR_ATTRIB[i+1] = A_BOLD;
        color += 6;
      }
    }

    switch (i + 1) {
      case 1:
          init_pair(1, COLOR_BLACK, COLOR_WHITE);
          break;
      case 2:
          init_pair(2, COLOR_WHITE, COLOR_BLACK);
          break;
      case COLOR_GENERAL:
          init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
                    find_color(background));
          break;
      case COLOR_MSGOUT:
          init_pair(i+1, ((color) ? find_color(color) : COLOR_CYAN),
                    find_color(background));
          break;
      case COLOR_MSGHL:
          init_pair(i+1, ((color) ? find_color(color) : COLOR_YELLOW),
                    find_color(background));
          break;
      case COLOR_STATUS:
          init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
                    find_color(backstatus));
          break;
      case COLOR_LOG:
          init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
                    find_color(background));
          break;
      case COLOR_ROSTER:
          init_pair(i+1, ((color) ? find_color(color) : COLOR_GREEN),
                    find_color(background));
          break;
      case COLOR_ROSTERSEL:
          init_pair(i+1, ((color) ? find_color(color) : COLOR_BLUE),
                    find_color(backselected));
          break;
      case COLOR_ROSTERSELNMSG:
          init_pair(i+1, ((color) ? find_color(color) : COLOR_RED),
                    find_color(backselected));
          break;
      case COLOR_ROSTERNMSG:
          init_pair(i+1, ((color) ? find_color(color) : COLOR_RED),
                    find_color(background));
          break;
      case COLOR_INFO:
          init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
                    find_color(background));
          break;
      case COLOR_MSGIN:
          init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
                    find_color(background));
          break;
      case COLOR_READMARK:
          init_pair(i+1, ((color) ? find_color(color) : COLOR_RED),
                    find_color(background));
          break;
      case COLOR_TIMESTAMP:
          init_pair(i+1, ((color) ? find_color(color) : COLOR_WHITE),
                    find_color(background));
          break;
    }
  }
  for (i = COLOR_max; i < (COLOR_max + COLORS); i++)
    init_pair(i, i-COLOR_max, find_color(background));

  if (!nickcols) {
    char *ncolors = g_strdup(settings_opt_get("nick_colors"));
    if (ncolors) {
      char *ncolor_start, *ncolor_end;
      ncolor_start = ncolor_end = ncolors;

      while (*ncolor_end)
        ncolor_end++;

      while (ncolors < ncolor_end && *ncolors) {
        if ((*ncolors == ' ') || (*ncolors == '\t')) {
          ncolors++;
        } else {
          char *end = ncolors;
          ccolor *cl;
          while (*end && (*end != ' ') && (*end != '\t'))
            end++;
          *end = '\0';
          cl = get_user_color(ncolors);
          if (!cl) {
            scr_LogPrint(LPRINT_NORMAL, "Unknown color %s", ncolors);
          } else {
            nickcols = g_realloc(nickcols, (++nickcolcount) * sizeof *nickcols);
            nickcols[nickcolcount-1] = cl;
          }
          ncolors = end+1;
        }
      }
      g_free(ncolor_start);
    }
    if (!nickcols) { // Fallback to have something
      nickcolcount = 1;
      nickcols = g_new(ccolor*, 1);
      *nickcols = g_new(ccolor, 1);
      (*nickcols)->color_pair = COLOR_GENERAL;
      (*nickcols)->color_attrib = A_NORMAL;
    }
  }

  colors_stalled = FALSE;
}

static void init_keycodes(void)
{
  add_keyseq("O5A", MKEY_EQUIV, 521); // Ctrl-Up
  add_keyseq("O5B", MKEY_EQUIV, 514); // Ctrl-Down
  add_keyseq("O5C", MKEY_EQUIV, 518); // Ctrl-Right
  add_keyseq("O5D", MKEY_EQUIV, 516); // Ctrl-Left
  add_keyseq("O6A", MKEY_EQUIV, 520); // Shift-Up
  add_keyseq("O6B", MKEY_EQUIV, 513); // Shift-Down
  add_keyseq("O6C", MKEY_EQUIV, 402); // Shift-Right
  add_keyseq("O6D", MKEY_EQUIV, 393); // Shift-Left
  add_keyseq("O2A", MKEY_EQUIV, 520); // Shift-Up
  add_keyseq("O2B", MKEY_EQUIV, 513); // Shift-Down
  add_keyseq("O2C", MKEY_EQUIV, 402); // Shift-Right
  add_keyseq("O2D", MKEY_EQUIV, 393); // Shift-Left
  add_keyseq("[5^", MKEY_CTRL_PGUP, 0);   // Ctrl-PageUp
  add_keyseq("[6^", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown
  add_keyseq("[5@", MKEY_CTRL_SHIFT_PGUP, 0);   // Ctrl-Shift-PageUp
  add_keyseq("[6@", MKEY_CTRL_SHIFT_PGDOWN, 0); // Ctrl-Shift-PageDown
  add_keyseq("[7@", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home
  add_keyseq("[8@", MKEY_CTRL_SHIFT_END, 0);  // Ctrl-Shift-End
  add_keyseq("[8^", MKEY_CTRL_END, 0);  // Ctrl-End
  add_keyseq("[7^", MKEY_CTRL_HOME, 0); // Ctrl-Home
  add_keyseq("[2^", MKEY_CTRL_INS, 0);  // Ctrl-Insert
  add_keyseq("[3^", MKEY_CTRL_DEL, 0);  // Ctrl-Delete

  // Xterm
  add_keyseq("[1;5A", MKEY_EQUIV, 521); // Ctrl-Up
  add_keyseq("[1;5B", MKEY_EQUIV, 514); // Ctrl-Down
  add_keyseq("[1;5C", MKEY_EQUIV, 518); // Ctrl-Right
  add_keyseq("[1;5D", MKEY_EQUIV, 516); // Ctrl-Left
  add_keyseq("[1;6A", MKEY_EQUIV, 520); // Ctrl-Shift-Up
  add_keyseq("[1;6B", MKEY_EQUIV, 513); // Ctrl-Shift-Down
  add_keyseq("[1;6C", MKEY_EQUIV, 402); // Ctrl-Shift-Right
  add_keyseq("[1;6D", MKEY_EQUIV, 393); // Ctrl-Shift-Left
  add_keyseq("[1;6H", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home
  add_keyseq("[1;6F", MKEY_CTRL_SHIFT_END, 0);  // Ctrl-Shift-End
  add_keyseq("[1;2A", MKEY_EQUIV, 521); // Shift-Up
  add_keyseq("[1;2B", MKEY_EQUIV, 514); // Shift-Down
  add_keyseq("[5;5~", MKEY_CTRL_PGUP, 0);   // Ctrl-PageUp
  add_keyseq("[6;5~", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown
  add_keyseq("[1;5F", MKEY_CTRL_END, 0);  // Ctrl-End
  add_keyseq("[1;5H", MKEY_CTRL_HOME, 0); // Ctrl-Home
  add_keyseq("[2;5~", MKEY_CTRL_INS, 0);  // Ctrl-Insert
  add_keyseq("[3;5~", MKEY_CTRL_DEL, 0);  // Ctrl-Delete

  // PuTTY
  add_keyseq("[A", MKEY_EQUIV, 521); // Ctrl-Up
  add_keyseq("[B", MKEY_EQUIV, 514); // Ctrl-Down
  add_keyseq("[C", MKEY_EQUIV, 518); // Ctrl-Right
  add_keyseq("[D", MKEY_EQUIV, 516); // Ctrl-Left

  // screen
  add_keyseq("Oa", MKEY_EQUIV, 521); // Ctrl-Up
  add_keyseq("Ob", MKEY_EQUIV, 514); // Ctrl-Down
  add_keyseq("Oc", MKEY_EQUIV, 518); // Ctrl-Right
  add_keyseq("Od", MKEY_EQUIV, 516); // Ctrl-Left
  add_keyseq("[a", MKEY_EQUIV, 520); // Shift-Up
  add_keyseq("[b", MKEY_EQUIV, 513); // Shift-Down
  add_keyseq("[c", MKEY_EQUIV, 402); // Shift-Right
  add_keyseq("[d", MKEY_EQUIV, 393); // Shift-Left
  add_keyseq("[5$", MKEY_SHIFT_PGUP, 0);   // Shift-PageUp
  add_keyseq("[6$", MKEY_SHIFT_PGDOWN, 0); // Shift-PageDown

  // VT100
  add_keyseq("[H", MKEY_EQUIV, KEY_HOME); // Home
  add_keyseq("[F", MKEY_EQUIV, KEY_END);  // End

  // Konsole Linux
  add_keyseq("[1~", MKEY_EQUIV, KEY_HOME); // Home
  add_keyseq("[4~", MKEY_EQUIV, KEY_END);  // End
}

//  scr_init_bindings()
// Create default key bindings
// Return 0 if error and 1 if none
void scr_init_bindings(void)
{
  GString *sbuf = g_string_new("");

  // Common backspace key codes: 8, 127
  settings_set(SETTINGS_TYPE_BINDING, "8", "iline char_bdel");    // Ctrl-h
  settings_set(SETTINGS_TYPE_BINDING, "127", "iline char_bdel");
  g_string_printf(sbuf, "%d", KEY_BACKSPACE);
  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_bdel");
  g_string_printf(sbuf, "%d", KEY_DC);
  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_fdel");
  g_string_printf(sbuf, "%d", KEY_LEFT);
  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline bchar");
  g_string_printf(sbuf, "%d", KEY_RIGHT);
  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline fchar");
  settings_set(SETTINGS_TYPE_BINDING, "7", "iline compl_cancel"); // Ctrl-g
  g_string_printf(sbuf, "%d", KEY_UP);
  settings_set(SETTINGS_TYPE_BINDING, sbuf->str,
               "iline hist_beginning_search_bwd");
  g_string_printf(sbuf, "%d", KEY_DOWN);
  settings_set(SETTINGS_TYPE_BINDING, sbuf->str,
               "iline hist_beginning_search_fwd");
  g_string_printf(sbuf, "%d", KEY_PPAGE);
  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster up");
  g_string_printf(sbuf, "%d", KEY_NPAGE);
  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster down");
  g_string_printf(sbuf, "%d", KEY_HOME);
  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_start");
  settings_set(SETTINGS_TYPE_BINDING, "1", "iline iline_start");  // Ctrl-a
  g_string_printf(sbuf, "%d", KEY_END);
  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_end");
  settings_set(SETTINGS_TYPE_BINDING, "5", "iline iline_end");    // Ctrl-e
  // Ctrl-o (accept-line-and-down-history):
  settings_set(SETTINGS_TYPE_BINDING, "15", "iline iline_accept_down_hist");
  settings_set(SETTINGS_TYPE_BINDING, "21", "iline iline_bdel");  // Ctrl-u
  g_string_printf(sbuf, "%d", KEY_EOL);
  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_fdel");
  settings_set(SETTINGS_TYPE_BINDING, "11", "iline iline_fdel");  // Ctrl-k
  settings_set(SETTINGS_TYPE_BINDING, "16", "buffer up");         // Ctrl-p
  settings_set(SETTINGS_TYPE_BINDING, "14", "buffer down");       // Ctrl-n
  settings_set(SETTINGS_TYPE_BINDING, "20", "iline char_swap");   // Ctrl-t
  settings_set(SETTINGS_TYPE_BINDING, "23", "iline word_bdel");   // Ctrl-w
  settings_set(SETTINGS_TYPE_BINDING, "M98", "iline bword");      // Meta-b
  settings_set(SETTINGS_TYPE_BINDING, "M102", "iline fword");     // Meta-f
  settings_set(SETTINGS_TYPE_BINDING, "M100", "iline word_fdel"); // Meta-d
  // Ctrl-Left  (2 codes):
  settings_set(SETTINGS_TYPE_BINDING, "515", "iline bword");
  settings_set(SETTINGS_TYPE_BINDING, "516", "iline bword");
  // Ctrl-Right (2 codes):
  settings_set(SETTINGS_TYPE_BINDING, "517", "iline fword");
  settings_set(SETTINGS_TYPE_BINDING, "518", "iline fword");
  settings_set(SETTINGS_TYPE_BINDING, "12", "screen_refresh");    // Ctrl-l
  settings_set(SETTINGS_TYPE_BINDING, "27", "chat_disable --show-roster");// Esc
  settings_set(SETTINGS_TYPE_BINDING, "M27", "chat_disable");     // Esc-Esc
  settings_set(SETTINGS_TYPE_BINDING, "4", "iline send_multiline"); // Ctrl-d
  settings_set(SETTINGS_TYPE_BINDING, "M117", "iline word_upcase"); // Meta-u
  settings_set(SETTINGS_TYPE_BINDING, "M108", "iline word_downcase"); // Meta-l
  settings_set(SETTINGS_TYPE_BINDING, "M99", "iline word_capit"); // Meta-c

  settings_set(SETTINGS_TYPE_BINDING, "265", "help"); // Bind F1 to help...

  g_string_free(sbuf, TRUE);
}

//  is_speckey(key)
// Return TRUE if key is a special code, i.e. no char should be displayed on
// the screen.  It's not very nice, it's a workaround for the systems where
// isprint(KEY_PPAGE) returns TRUE...
static int is_speckey(int key)
{
  switch (key) {
    case 127:
    case 393:
    case 402:
    case KEY_BACKSPACE:
    case KEY_DC:
    case KEY_LEFT:
    case KEY_RIGHT:
    case KEY_UP:
    case KEY_DOWN:
    case KEY_PPAGE:
    case KEY_NPAGE:
    case KEY_HOME:
    case KEY_END:
    case KEY_EOL:
        return TRUE;
  }

  // Fn keys
  if (key >= 265 && key < 265+12)
    return TRUE;

  // Special key combinations
  if (key >= 513 && key <= 521)
    return TRUE;

  return FALSE;
}

void scr_init_locale_charset(void)
{
  setlocale(LC_ALL, "");
#ifdef HAVE_LOCALCHARSET_H
  LocaleCharSet = locale_charset();
#else
  LocaleCharSet = nl_langinfo(CODESET);
#endif
  utf8_mode = (strcmp(LocaleCharSet, "UTF-8") == 0);
}

gboolean scr_curses_status(void)
{
  return Curses;
}

static gchar *scr_color_guard(const gchar *key, const gchar *new_value)
{
  if (g_strcmp0(settings_opt_get(key), new_value))
    colors_stalled = TRUE;
  return g_strdup(new_value);
}

void scr_init_curses(void)
{
  /* Key sequences initialization */
  init_keycodes();

  initscr();
  raw();
  noecho();
  nonl();
  intrflush(stdscr, FALSE);
  start_color();
  use_default_colors();
#ifdef NCURSES_MOUSE_VERSION
  if (settings_opt_get_int("use_mouse"))
    mousemask(ALL_MOUSE_EVENTS, NULL);
#endif

  if (settings_opt_get("escdelay")) {
#ifdef HAVE_ESCDELAY
    ESCDELAY = (unsigned) settings_opt_get_int("escdelay");
#else
    scr_LogPrint(LPRINT_LOGNORM, "ERROR: no ESCDELAY support.");
#endif
  }

  parse_colors();

  settings_set_guard("color_background", scr_color_guard);
  settings_set_guard("color_general", scr_color_guard);
  settings_set_guard("color_info", scr_color_guard);
  settings_set_guard("color_msgin", scr_color_guard);
  settings_set_guard("color_msgout", scr_color_guard);
  settings_set_guard("color_msghl", scr_color_guard);
  settings_set_guard("color_bgstatus", scr_color_guard);
  settings_set_guard("color_status", scr_color_guard);
  settings_set_guard("color_log", scr_color_guard);
  settings_set_guard("color_roster", scr_color_guard);
  settings_set_guard("color_bgrostersel", scr_color_guard);
  settings_set_guard("color_rostersel", scr_color_guard);
  settings_set_guard("color_rosterselmsg", scr_color_guard);
  settings_set_guard("color_rosternewmsg", scr_color_guard);
  settings_set_guard("color_timestamp", scr_color_guard);

  getmaxyx(stdscr, maxY, maxX);
  Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT;
  // Note scr_draw_main_window() should be called early after scr_init_curses()
  // to update Log_Win_Height and set max{X,Y}

  inputLine[0] = 0;
  ptr_inputline = inputLine;

  Curses = TRUE;

  g_log_set_handler("GLib", G_LOG_LEVEL_MASK, scr_glog_print, NULL);
  return;
}

void scr_terminate_curses(void)
{
  if (!Curses) return;
  clear();
  refresh();
  endwin();
  Curses = FALSE;
  return;
}

void scr_beep(void)
{
  beep();
}

// This and following belongs to dynamic setting of time prefix
static const char *timeprefixes[] = {
  "%m-%d %H:%M ",
  "%H:%M ",
  " "
};

static const char *spectimeprefixes[] = {
  "%m-%d %H:%M:%S   ",
  "%H:%M:%S   ",
  "   "
};

static int timepreflengths[] = {
  // (length of the corresponding timeprefix + 5)
  17,
  11,
  6
};

static const char *gettprefix(void)
{
  guint n = settings_opt_get_int("time_prefix");
  return timeprefixes[(n < 3 ? n : 0)];
}

static const char *getspectprefix(void)
{
  guint n = settings_opt_get_int("time_prefix");
  return spectimeprefixes[(n < 3 ? n : 0)];
}

guint scr_getprefixwidth(void)
{
  guint n = settings_opt_get_int("time_prefix");
  return timepreflengths[(n < 3 ? n : 0)];
}

guint scr_gettextwidth(void)
{
  return maxX - Roster_Width - scr_getprefixwidth();
}

guint scr_gettextheight(void)
{
  // log window, two status bars and one input line
  return maxY - Log_Win_Height - 3;
}

guint scr_getlogwinheight(void)
{
  if (Log_Win_Height >= 2)
    return Log_Win_Height - 2;
  return 0;
}

//  scr_print_logwindow(string)
// Display the string in the log window.
// Note: The string must be in the user's locale!
void scr_print_logwindow(const char *string)
{
  time_t timestamp;
  char strtimestamp[64];

  timestamp = time(NULL);
  strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(&timestamp));
  if (Curses) {
    wprintw(logWnd, "\n%s %s", strtimestamp, string);
    update_panels();
  } else {
    printf("%s %s\n", strtimestamp, string);
  }
}

//  scr_log_print(...)
// Display a message in the log window and in the status buffer.
// Add the message to the tracelog file if the log flag is set.
// This function will convert from UTF-8 unless the LPRINT_NOTUTF8 flag is set.
void scr_log_print(unsigned int flag, const char *fmt, ...)
{
  time_t timestamp;
  char strtimestamp[64];
  char *buffer, *btext;
  char *convbuf1 = NULL, *convbuf2 = NULL;
  va_list ap;

  if (!(flag & ~LPRINT_NOTUTF8)) return; // Shouldn't happen

  timestamp = time(NULL);
  strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(&timestamp));
  va_start(ap, fmt);
  btext = g_strdup_vprintf(fmt, ap);
  va_end(ap);

  if (flag & LPRINT_NORMAL) {
    char *buffer_locale;
    char *buf_specialwindow;

    buffer = g_strdup_printf("%s %s", strtimestamp, btext);

    // Convert buffer to current locale for wprintw()
    if (!(flag & LPRINT_NOTUTF8))
      buffer_locale = convbuf1 = from_utf8(buffer);
    else
      buffer_locale = buffer;

    if (!buffer_locale) {
      wprintw(logWnd,
              "\n%s*Error: cannot convert string to locale.", strtimestamp);
      update_panels();
      g_free(buffer);
      g_free(btext);
      return;
    }

    // For the special status buffer, we need utf-8, but without the timestamp
    if (flag & LPRINT_NOTUTF8)
      buf_specialwindow = convbuf2 = to_utf8(btext);
    else
      buf_specialwindow = btext;

    if (Curses) {
      wprintw(logWnd, "\n%s", buffer_locale);
      update_panels();
      scr_write_in_window(NULL, buf_specialwindow, timestamp,
                          HBB_PREFIX_SPECIAL, FALSE, 0, NULL);
    } else {
      printf("%s\n", buffer_locale);
      // ncurses are not initialized yet, so we call directly hbuf routine
      hbuf_add_line(&statushbuf, buf_specialwindow, timestamp,
        HBB_PREFIX_SPECIAL, 0, 0, 0, NULL);
    }

    g_free(convbuf1);
    g_free(convbuf2);
    g_free(buffer);
  }

  if (flag & (LPRINT_LOG|LPRINT_DEBUG)) {
    strftime(strtimestamp, 23, "[%Y-%m-%d %H:%M:%S]", localtime(&timestamp));
    buffer = g_strdup_printf("%s %s\n", strtimestamp, btext);
    ut_write_log(flag, buffer);
    g_free(buffer);
  }
  g_free(btext);
}

// This is a GLogFunc for Glib log messages
static void scr_glog_print(const gchar *log_domain, GLogLevelFlags log_level,
                           const gchar *message, gpointer user_data)
{
  scr_log_print(LPRINT_NORMAL, "[%s] %s", log_domain, message);
}

static winbuf *scr_search_window(const char *winId, int special)
{
  char *id;
  winbuf *wbp;

  if (special)
    return statusWindow; // Only one special window atm.

  if (!winId)
    return NULL;

  id = g_strdup(winId);
  mc_strtolower(id);
  wbp = g_hash_table_lookup(winbufhash, id);
  g_free(id);
  return wbp;
}

int scr_buddy_buffer_exists(const char *bjid)
{
  return (scr_search_window(bjid, FALSE) != NULL);
}

//  scr_new_buddy(title, dontshow)
// Note: title (aka winId/jid) can be NULL for special buffers
static winbuf *scr_new_buddy(const char *title, int dont_show)
{
  winbuf *tmp;
  char *id;

  tmp = g_new0(winbuf, 1);

  tmp->win = activechatWnd;
  tmp->panel = activechatPanel;

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

  // If title is NULL, this is a special buffer
  if (!title) {
    tmp->bd = g_new0(buffdata, 1);
    return tmp;
  }

  id = hlog_get_log_jid(title);
  if (id) {
    // This is a symlinked history log file.
    // Let's check if the target JID buffer has already been created.
    winbuf *wb = scr_search_window(id, FALSE);
    if (!wb)
      wb = scr_new_buddy(id, TRUE);
    tmp->bd = wb->bd;
    tmp->bd->refcount++;
    g_free(id);
  } else {  // Load buddy history from file (if enabled)
    tmp->bd = g_new0(buffdata, 1);
    hlog_read_history(title, &tmp->bd->hbuf,
                      maxX - Roster_Width - scr_getprefixwidth());

    // Set a readmark to separate new content
    hbuf_set_readmark(tmp->bd->hbuf, TRUE);
  }

  id = g_strdup(title);
  mc_strtolower(id);
  g_hash_table_insert(winbufhash, id, tmp);

  return tmp;
}

//  scr_line_prefix(line, pref, preflen)
// Use data from the hbb_line structure and write the prefix
// to pref (not exceeding preflen, trailing null byte included).
size_t scr_line_prefix(hbb_line *line, char *pref, guint preflen)
{
  char date[64];
  size_t timepreflen = 0;

  if (line->timestamp &&
      !(line->flags & (HBB_PREFIX_SPECIAL|HBB_PREFIX_CONT))) {
    timepreflen = strftime(date, 30, gettprefix(), localtime(&line->timestamp));
  } else
    strcpy(date, "           ");

  if (!(line->flags & HBB_PREFIX_CONT)) {
    if (line->flags & HBB_PREFIX_INFO) {
      char dir = '*';
      if (line->flags & HBB_PREFIX_IN)
        dir = '<';
      else if (line->flags & HBB_PREFIX_OUT)
        dir = '>';
      g_snprintf(pref, preflen, "%s*%c* ", date, dir);
    } else if (line->flags & HBB_PREFIX_ERR) {
      char dir = '#';
      if (line->flags & HBB_PREFIX_IN)
        dir = '<';
      else if (line->flags & HBB_PREFIX_OUT)
        dir = '>';
      g_snprintf(pref, preflen, "%s#%c# ", date, dir);
    } else if (line->flags & HBB_PREFIX_IN) {
      char cryptflag;
      if (line->flags & HBB_PREFIX_PGPCRYPT)
        cryptflag = '~';
      else if (line->flags & HBB_PREFIX_OTRCRYPT)
        cryptflag = 'O';
      else
        cryptflag = '=';
      g_snprintf(pref, preflen, "%s<%c= ", date, cryptflag);
    } else if (line->flags & HBB_PREFIX_OUT) {
      char cryptflag, receiptflag;
      if (line->flags & HBB_PREFIX_PGPCRYPT)
        cryptflag = '~';
      else if (line->flags & HBB_PREFIX_OTRCRYPT)
        cryptflag = 'O';
      else
        cryptflag = '-';
      if (line->flags & HBB_PREFIX_RECEIPT)
        receiptflag = 'r';
      else
        receiptflag = '-';
      g_snprintf(pref, preflen, "%s%c%c> ", date, receiptflag, cryptflag);
    } else if (line->flags & HBB_PREFIX_SPECIAL) {
      timepreflen = strftime(date, 30, getspectprefix(), localtime(&line->timestamp));
      g_snprintf(pref, preflen, "%s   ", date);
    } else {
      g_snprintf(pref, preflen, "%s    ", date);
    }
  } else {
    g_snprintf(pref, preflen, "                ");
  }
  return timepreflen;
}

//  scr_update_window()
// (Re-)Display the given chat window.
static void scr_update_window(winbuf *win_entry)
{
  int n, mark_offset = 0;
  guint prefixwidth;
  char pref[96];
  hbb_line **lines, *line;
  GList *hbuf_head;
  int color = COLOR_GENERAL;
  bool readmark = FALSE;
  bool skipline = FALSE;
  int autolock;

  autolock = settings_opt_get_int("buffer_smart_scrolling");

  prefixwidth = scr_getprefixwidth();
  prefixwidth = MIN(prefixwidth, sizeof pref);

  // Should the window be empty?
  if (win_entry->bd->cleared) {
    werase(win_entry->win);
    if (autolock && win_entry->bd->lock)
      scr_buffer_scroll_lock(0);
    return;
  }

  // win_entry->bd->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->bd->top || (g_list_position(g_list_first(win_entry->bd->hbuf),
                                              win_entry->bd->top) == -1)) {
    // Move up CHAT_WIN_HEIGHT lines
    win_entry->bd->hbuf = g_list_last(win_entry->bd->hbuf);
    hbuf_head = win_entry->bd->hbuf;
    win_entry->bd->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++;
    }
    // If the buffer is locked, remember current "top" line for the next time.
    if (win_entry->bd->lock)
      win_entry->bd->top = hbuf_head;
  } else
    hbuf_head = win_entry->bd->top;

  // Get the last CHAT_WIN_HEIGHT lines, and one more to detect scroll.
  lines = hbuf_get_lines(hbuf_head, CHAT_WIN_HEIGHT+1);

  if (CHAT_WIN_HEIGHT > 1) {
    // Do we have a read mark?
    for (n = 0; n < CHAT_WIN_HEIGHT; n++) {
      line = *(lines+n);
      if (line) {
        if (line->flags & HBB_PREFIX_READMARK) {
          // If this is not the last line, we'll display a mark
          if (n+1 < CHAT_WIN_HEIGHT && *(lines+n+1)) {
            readmark = TRUE;
            skipline = TRUE;
            mark_offset = -1;
          }
        }
      } else if (readmark) {
        // There will be empty lines, so we don't need to skip the first line
        skipline = FALSE;
        mark_offset = 0;
      }
    }
  }

  // Display the lines
  for (n = 0 ; n < CHAT_WIN_HEIGHT; n++) {
    int timelen;
    int winy = n + mark_offset;
    wmove(win_entry->win, winy, 0);
    line = *(lines+n);
    if (line) {
      if (skipline)
        goto scr_update_window_skipline;

      if (line->flags & HBB_PREFIX_HLIGHT_OUT)
        color = COLOR_MSGOUT;
      else if (line->flags & HBB_PREFIX_HLIGHT)
        color = COLOR_MSGHL;
      else if (line->flags & HBB_PREFIX_INFO)
        color = COLOR_INFO;
      else if (line->flags & HBB_PREFIX_IN)
        color = COLOR_MSGIN;
      else
        color = COLOR_GENERAL;

      if (color != COLOR_GENERAL)
        wattrset(win_entry->win, get_color(color));

      // Generate the prefix area and display it

      timelen = scr_line_prefix(line, pref, prefixwidth);
      if (timelen && line->flags & HBB_PREFIX_DELAYED) {
        char tmp;

        tmp = pref[timelen];
        pref[timelen] = '\0';
        wattrset(win_entry->win, get_color(COLOR_TIMESTAMP));
        wprintw(win_entry->win, pref);
        pref[timelen] = tmp;
        wattrset(win_entry->win, get_color(color));
        wprintw(win_entry->win, pref+timelen);
      } else
        wprintw(win_entry->win, pref);

      // Make sure we are at the right position
      wmove(win_entry->win, winy, prefixwidth-1);

      // The MUC nick - overwrite with proper color
      if (line->mucnicklen) {
        char *mucjid;
        char tmp;
        nickcolor *actual = NULL;
        muccoltype type, *typetmp;

        // Store the char after the nick
        tmp = line->text[line->mucnicklen];
        type = glob_muccol;
        // Terminate the string after the nick
        line->text[line->mucnicklen] = '\0';
        mucjid = g_utf8_strdown(CURRENT_JID, -1);
        if (muccolors) {
          typetmp = g_hash_table_lookup(muccolors, mucjid);
          if (typetmp)
            type = *typetmp;
        }
        g_free(mucjid);
        // Need to generate a color for the specified nick?
        if ((type == MC_ALL) && (!nickcolors ||
            !g_hash_table_lookup(nickcolors, line->text))) {
          char *snick, *mnick;
          nickcolor *nc;
          const char *p = line->text;
          unsigned int nicksum = 0;
          snick = g_strdup(line->text);
          mnick = g_strdup(line->text);
          nc = g_new(nickcolor, 1);
          ensure_string_htable(&nickcolors, NULL);
          while (*p)
            nicksum += *p++;
          nc->color = nickcols[nicksum % nickcolcount];
          nc->manual = FALSE;
          *snick = '<';
          snick[strlen(snick)-1] = '>';
          *mnick = '*';
          mnick[strlen(mnick)-1] = ' ';
          // Insert them
          g_hash_table_insert(nickcolors, snick, nc);
          g_hash_table_insert(nickcolors, mnick, nc);
        }
        if (nickcolors)
          actual = g_hash_table_lookup(nickcolors, line->text);
        if (actual && ((type == MC_ALL) || (actual->manual))
            && (line->flags & HBB_PREFIX_IN) &&
           (!(line->flags & HBB_PREFIX_HLIGHT_OUT)))
          wattrset(win_entry->win, compose_color(actual->color));
        wprintw(win_entry->win, "%s", line->text);
        // Return the char
        line->text[line->mucnicklen] = tmp;
        // Return the color back
        wattrset(win_entry->win, get_color(color));
      }

      // Display text line
      wprintw(win_entry->win, "%s", line->text+line->mucnicklen);
      wclrtoeol(win_entry->win);

scr_update_window_skipline:
      skipline = FALSE;
      if (readmark && line->flags & HBB_PREFIX_READMARK) {
        int i, w;
        mark_offset++;

        // Display the mark
        winy = n + mark_offset;
        wmove(win_entry->win, winy, 0);
        color = COLOR_READMARK;
        wattrset(win_entry->win, get_color(color));
        g_snprintf(pref, prefixwidth, "             == ");
        wprintw(win_entry->win, pref);
        w = scr_gettextwidth() / 3;
        for (i=0; i<w; i++)
          wprintw(win_entry->win, "== ");
        wclrtoeol(win_entry->win);
        wattrset(win_entry->win, get_color(COLOR_GENERAL));
      }

      // Restore default ("general") color
      if (color != COLOR_GENERAL)
        wattrset(win_entry->win, get_color(COLOR_GENERAL));

      g_free(line->text);
      g_free(line);
    } else {
      wclrtobot(win_entry->win);
      break;
    }
  }
  line = *(lines+CHAT_WIN_HEIGHT); //line is scrolled out and never written
  if (line) {
    if (autolock && !win_entry->bd->lock) {
      if (!hbuf_jump_readmark(hbuf_head))
        scr_buffer_readmark(TRUE);
      scr_buffer_scroll_lock(1);
    }
    g_free(line->text);
    g_free(line);
  } else if (autolock && win_entry->bd->lock) {
    scr_buffer_scroll_lock(0);
  }

  g_free(lines);
}

static winbuf *scr_create_window(const char *winId, int special, int dont_show)
{
  if (special) {
    if (!statusWindow) {
      statusWindow = scr_new_buddy(NULL, dont_show);
      statusWindow->bd->hbuf = statushbuf;
    }
    return statusWindow;
  } else {
    return scr_new_buddy(winId, dont_show);
  }
}

//  scr_show_window()
// Display the chat window with the given identifier.
// "special" must be true if this is a special buffer window.
static void scr_show_window(const char *winId, int special)
{
  winbuf *win_entry;

  win_entry = scr_search_window(winId, special);

  if (!win_entry) {
    win_entry = scr_create_window(winId, special, FALSE);
  }

  top_panel(win_entry->panel);
  currentWindow = win_entry;
  chatmode = TRUE;
  if (!win_entry->bd->lock)
    roster_msg_setflag(winId, special, FALSE);
  if (!special)
    roster_setflags(winId, ROSTER_FLAG_LOCK, TRUE);
  scr_update_roster();

  // Refresh the window
  scr_update_window(win_entry);

  // Finished :)
  update_panels();

  top_panel(inputPanel);
}

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

  if (!current_buddy) {
    bjid = NULL;
  } else {
    bjid = CURRENT_JID;
    if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL) {
      scr_show_window(buddy_getname(BUDDATA(current_buddy)), TRUE);
      return;
    }
  }

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

  scr_show_window(bjid, FALSE);
}

//  scr_update_buddy_window()
// (Re)Display the current window.
// If chatmode is enabled, call scr_show_buddy_window(),
// else display the chat window.
inline void scr_update_buddy_window(void)
{
  if (chatmode) {
    scr_show_buddy_window();
    return;
  }

  top_panel(chatPanel);
  top_panel(inputPanel);
}

//  scr_write_in_window()
// Write some text in the winId window (this usually is a jid).
// Use winId == NULL for the special status buffer.
// Lines are splitted when they are too long to fit in the chat window.
// If this window doesn't exist, it is created.
static void scr_write_in_window(const char *winId, const char *text,
                                time_t timestamp, unsigned int prefix_flags,
                                int force_show, unsigned mucnicklen,
                                gpointer xep184)
{
  winbuf *win_entry;
  char *text_locale;
  int dont_show = FALSE;
  int special;
  guint num_history_blocks;
  bool setmsgflg = FALSE;
  bool clearmsgflg = FALSE;
  char *nicktmp, *nicklocaltmp;

  // Look for the window entry.
  special = (winId == NULL);
  win_entry = scr_search_window(winId, special);

  // 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) {
    win_entry = scr_create_window(winId, special, dont_show);
  }

  // The message must be displayed -> update top pointer
  if (win_entry->bd->cleared)
    win_entry->bd->top = g_list_last(win_entry->bd->hbuf);

  // Make sure we do not free the buffer while it's locked or when
  // top is set.
  if (win_entry->bd->lock || win_entry->bd->top)
    num_history_blocks = 0U;
  else
    num_history_blocks = get_max_history_blocks();

  text_locale = from_utf8(text);
  // Convert the nick alone and compute its length
  if (mucnicklen) {
    nicktmp = g_strndup(text, mucnicklen);
    nicklocaltmp = from_utf8(nicktmp);
    if (nicklocaltmp)
      mucnicklen = strlen(nicklocaltmp);
    g_free(nicklocaltmp);
    g_free(nicktmp);
  }
  hbuf_add_line(&win_entry->bd->hbuf, text_locale, timestamp, prefix_flags,
                maxX - Roster_Width - scr_getprefixwidth(), num_history_blocks,
                mucnicklen, xep184);
  g_free(text_locale);

  if (win_entry->bd->cleared) {
    win_entry->bd->cleared = FALSE;
    if (g_list_next(win_entry->bd->top))
      win_entry->bd->top = g_list_next(win_entry->bd->top);
  }

  // Make sure the last line appears in the window; update top if necessary
  if (!win_entry->bd->lock && win_entry->bd->top) {
    int dist;
    GList *first = g_list_first(win_entry->bd->hbuf);
    dist = g_list_position(first, g_list_last(win_entry->bd->hbuf)) -
           g_list_position(first, win_entry->bd->top);
    if (dist >= CHAT_WIN_HEIGHT)
      win_entry->bd->top = NULL;
  }

  if (!dont_show) {
    if (win_entry->bd->lock)
      setmsgflg = TRUE;
    else
      // If this is an outgoing message, remove the readmark
      if (!special && (prefix_flags & (HBB_PREFIX_OUT|HBB_PREFIX_HLIGHT_OUT)))
        hbuf_set_readmark(win_entry->bd->hbuf, FALSE);
    // Show and refresh the window
    top_panel(win_entry->panel);
    scr_update_window(win_entry);
    top_panel(inputPanel);
    update_panels();
  } else if (settings_opt_get_int("clear_unread_on_carbon") &&
             prefix_flags & HBB_PREFIX_OUT &&
             prefix_flags & HBB_PREFIX_CARBON) {
    clearmsgflg = TRUE;
  } else if (!(prefix_flags & HBB_PREFIX_NOFLAG)) {
    setmsgflg = TRUE;
  }
  if (!special) {
    if (clearmsgflg) {
      roster_msg_setflag(winId, FALSE, FALSE);
      scr_update_roster();
    } else if (setmsgflg) {
      roster_msg_setflag(winId, FALSE, TRUE);
      scr_update_roster();
    }
  }
}

static char *attention_sign_guard(const gchar *key, const gchar *new_value)
{
  scr_update_roster();
  if (g_strcmp0(settings_opt_get(key), new_value)) {
    guint sign;
    char *c;
    if (!new_value || !*new_value)
      return NULL;
    sign = get_char(new_value);
    c = next_char((char*)new_value);
    if (get_char_width(new_value) != 1 || !iswprint(sign) || *c) {
      scr_log_print(LPRINT_NORMAL, "attention_char value is invalid.");
      return NULL;
    }
    // The new value looks good (1-char  wide and printable)
    return g_strdup(new_value);
  }
  return g_strdup(new_value);
}

//  scr_init_settings()
// Create guards for UI settings
void scr_init_settings(void)
{
  settings_set_guard("attention_char", attention_sign_guard);
}

static unsigned int attention_sign(void)
{
  const char *as = settings_opt_get("attention_char");
  if (!as)
      return DEFAULT_ATTENTION_CHAR;
  return get_char(as);
}

//  scr_update_main_status(forceupdate)
// Redraw the main (bottom) status line.
// You can set forceupdate to FALSE in order to optimize screen refresh
// if you call top_panel()/update_panels() later.
void scr_update_main_status(int forceupdate)
{
  char *sm = from_utf8(xmpp_getstatusmsg());
  const char *info = settings_opt_get("info");
  guint prio = 0;
  gpointer unread_ptr;
  guint unreadchar;

  unread_ptr = unread_msg(NULL);
  if (unread_ptr) {
    prio = buddy_getuiprio(unread_ptr);
    // If there's an unerad buffer but no priority set, let's consider the
    // priority is 1.
    if (!prio && buddy_getflags(unread_ptr) & ROSTER_FLAG_MSG)
      prio = 1;
  }

  // Status bar unread message flag
  if (prio >= ROSTER_UI_PRIO_MUC_HL_MESSAGE)
    unreadchar = attention_sign();
  else if (prio > 0)
    unreadchar = '#';
  else
    unreadchar = ' ';

  werase(mainstatusWnd);
  if (info) {
    char *info_locale = from_utf8(info);
    mvwprintw(mainstatusWnd, 0, 0, "%lc[%c] %s %s", unreadchar,
              imstatus2char[xmpp_getstatus()],
              info_locale, (sm ? sm : ""));
    g_free(info_locale);
  } else
    mvwprintw(mainstatusWnd, 0, 0, "%lc[%c] %s", unreadchar,
              imstatus2char[xmpp_getstatus()], (sm ? sm : ""));
  if (forceupdate) {
    top_panel(inputPanel);
    update_panels();
  }
  g_free(sm);
}

//  scr_draw_main_window()
// Set fullinit to TRUE to also create panels.  Set it to FALSE for a resize.
//
// I think it could be improved a _lot_ but I'm really not an ncurses
// expert... :-\   Mikael.
//
void scr_draw_main_window(unsigned int fullinit)
{
  int requested_size;
  gchar *ver, *message;
  int chat_y_pos, chatstatus_y_pos, log_y_pos;
  int roster_x_pos, chat_x_pos;

  roster_no_leading_space = settings_opt_get_int("roster_no_leading_space");

  Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT;
  requested_size = settings_opt_get_int("log_win_height");
  if (requested_size > 0) {
    if (maxY > requested_size + 3)
      Log_Win_Height = requested_size + 2;
    else
      Log_Win_Height = ((maxY > 5) ? (maxY - 2) : 3);
  } else if (requested_size < 0) {
    Log_Win_Height = 3;
  }

  if (maxY < Log_Win_Height+2) {
    if (maxY < 5) {
      Log_Win_Height = 3;
      maxY = Log_Win_Height+2;
    } else {
      Log_Win_Height = maxY - 2;
    }
  }

  if (roster_hidden) {
    Roster_Width = 0;
  } else {
    requested_size = settings_opt_get_int("roster_width");
    if (requested_size > 1)
      Roster_Width = requested_size;
    else if (requested_size == 1)
      Roster_Width = 2;
    else
      Roster_Width = DEFAULT_ROSTER_WIDTH;
  }

  log_win_on_top = (settings_opt_get_int("log_win_on_top") == 1);
  roster_win_on_right = (settings_opt_get_int("roster_win_on_right") == 1);

  if (log_win_on_top) {
    chat_y_pos = Log_Win_Height-1;
    log_y_pos = 0;
    chatstatus_y_pos = Log_Win_Height-2;
  } else {
    chat_y_pos = 0;
    log_y_pos = CHAT_WIN_HEIGHT+1;
    chatstatus_y_pos = CHAT_WIN_HEIGHT;
  }

  if (roster_win_on_right) {
    roster_x_pos = maxX - Roster_Width;
    chat_x_pos = 0;
  } else {
    roster_x_pos = 0;
    chat_x_pos = Roster_Width;
  }

  if (fullinit) {
    if (!winbufhash)
      winbufhash = g_hash_table_new_full(g_str_hash, g_str_equal,
                                         g_free, g_free);
    /* Create windows */
    rosterWnd = newwin(CHAT_WIN_HEIGHT, Roster_Width, chat_y_pos, roster_x_pos);
    chatWnd   = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos,
                       chat_x_pos);
    activechatWnd = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos,
                           chat_x_pos);
    logWnd    = newwin(Log_Win_Height-2, maxX, log_y_pos, 0);
    chatstatusWnd = newwin(1, maxX, chatstatus_y_pos, 0);
    mainstatusWnd = newwin(1, maxX, maxY-2, 0);
    inputWnd  = newwin(1, maxX, maxY-1, 0);
    if (!rosterWnd || !chatWnd || !logWnd || !inputWnd) {
      scr_terminate_curses();
      fprintf(stderr, "Cannot create windows!\n");
      exit(EXIT_FAILURE);
    }
    wbkgd(rosterWnd,      get_color(COLOR_GENERAL));
    wbkgd(chatWnd,        get_color(COLOR_GENERAL));
    wbkgd(activechatWnd,  get_color(COLOR_GENERAL));
    wbkgd(logWnd,         get_color(COLOR_GENERAL));
    wbkgd(chatstatusWnd,  get_color(COLOR_STATUS));
    wbkgd(mainstatusWnd,  get_color(COLOR_STATUS));

    wattrset(logWnd,      get_color(COLOR_LOG));
  } else {
    /* Resize/move windows */
    wresize(rosterWnd, CHAT_WIN_HEIGHT, Roster_Width);
    wresize(chatWnd, CHAT_WIN_HEIGHT, maxX - Roster_Width);
    wresize(logWnd, Log_Win_Height-2, maxX);

    mvwin(chatWnd, chat_y_pos, chat_x_pos);
    mvwin(rosterWnd, chat_y_pos, roster_x_pos);
    mvwin(logWnd, log_y_pos, 0);

    // Resize & move chat status window
    wresize(chatstatusWnd, 1, maxX);
    mvwin(chatstatusWnd, chatstatus_y_pos, 0);
    // Resize & move main status window
    wresize(mainstatusWnd, 1, maxX);
    mvwin(mainstatusWnd, maxY-2, 0);
    // Resize & move input line window
    wresize(inputWnd, 1, maxX);
    mvwin(inputWnd, maxY-1, 0);

    werase(chatWnd);
  }

  /* Draw/init windows */

  ver = mcabber_version();
  message = g_strdup_printf("MCabber version %s.\n", ver);
  mvwprintw(chatWnd, 0, 0, message);
  mvwprintw(chatWnd, 1, 0, "http://mcabber.com/");
  g_free(ver);
  g_free(message);

  // Auto-scrolling in log window
  scrollok(logWnd, TRUE);


  if (fullinit) {
    // Enable keypad (+ special keys)
    keypad(inputWnd, TRUE);
#ifdef __MirBSD__
    wtimeout(inputWnd, 50 /* ms */);
#else
    nodelay(inputWnd, TRUE);
#endif

    // Create panels
    rosterPanel = new_panel(rosterWnd);
    chatPanel   = new_panel(chatWnd);
    activechatPanel = new_panel(activechatWnd);
    logPanel    = new_panel(logWnd);
    chatstatusPanel = new_panel(chatstatusWnd);
    mainstatusPanel = new_panel(mainstatusWnd);
    inputPanel  = new_panel(inputWnd);

    // Build the buddylist at least once, to make sure the special buffer
    // is added
    buddylist_defer_build();

    // Init prev_chatwidth; this variable will be used to prevent us
    // from rewrapping buffers when the width doesn't change.
    prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth();
    // Wrap existing status buffer lines
    hbuf_rebuild(&statushbuf, prev_chatwidth);

#ifndef UNICODE
    if (utf8_mode)
      scr_LogPrint(LPRINT_NORMAL,
                   "WARNING: Compiled without full UTF-8 support!");
#endif
  } else {
    // Update panels
    replace_panel(rosterPanel, rosterWnd);
    replace_panel(chatPanel, chatWnd);
    replace_panel(logPanel, logWnd);
    replace_panel(chatstatusPanel, chatstatusWnd);
    replace_panel(mainstatusPanel, mainstatusWnd);
    replace_panel(inputPanel, inputWnd);
  }

  // We'll need to redraw the roster
  scr_update_roster();
  return;
}

static void resize_win_buffer(gpointer key, gpointer value, gpointer data)
{
  winbuf *wbp = value;
  struct dimensions *dim = data;
  int chat_x_pos, chat_y_pos;
  int new_chatwidth;

  if (!(wbp && wbp->win))
    return;

  if (log_win_on_top)
    chat_y_pos = Log_Win_Height-1;
  else
    chat_y_pos = 0;

  if (roster_win_on_right)
    chat_x_pos = 0;
  else
    chat_x_pos = Roster_Width;

  // Resize/move buddy window
  wresize(wbp->win, dim->l, dim->c);
  mvwin(wbp->win, chat_y_pos, chat_x_pos);
  werase(wbp->win);
  // If a panel exists, replace the old window with the new
  if (wbp->panel)
    replace_panel(wbp->panel, wbp->win);
  // Redo line wrapping
  wbp->bd->top = hbuf_previous_persistent(wbp->bd->top);

  new_chatwidth = maxX - Roster_Width - scr_getprefixwidth();
  if (new_chatwidth != prev_chatwidth)
    hbuf_rebuild(&wbp->bd->hbuf, new_chatwidth);
}

//  scr_resize()
// Function called when the window is resized.
// - Resize windows
// - Rewrap lines in each buddy buffer
void scr_resize(void)
{
  struct dimensions dim;

  // First, update the global variables
  getmaxyx(stdscr, maxY, maxX);
  // scr_draw_main_window() will take care of maxY and Log_Win_Height

  // Make sure the cursor stays inside the window
  check_offset(0);

  // Resize windows and update panels
  scr_draw_main_window(FALSE);

  // Resize all buddy windows
  dim.l = CHAT_WIN_HEIGHT;
  dim.c = maxX - Roster_Width;
  if (dim.c < 1)
    dim.c = 1;

  // Resize all buffers
  g_hash_table_foreach(winbufhash, resize_win_buffer, &dim);

  // Resize/move special status buffer
  if (statusWindow)
    resize_win_buffer(NULL, statusWindow, &dim);

  // Update prev_chatwidth, now that all buffers have been resized
  prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth();

  // Refresh current buddy window
  if (chatmode)
    scr_show_buddy_window();
}

#ifdef USE_SIGWINCH
void sigwinch_resize(void)
{
  struct winsize size;
  if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) != -1)
    resizeterm(size.ws_row, size.ws_col);
  scr_resize();
}
#endif

//  scr_update_chat_status(forceupdate)
// Redraw the buddy status bar.
// Set forceupdate to TRUE if update_panels() must be called.
void scr_update_chat_status(int forceupdate)
{
  unsigned short btype, isgrp, ismuc, isspe;
  const char *btypetext = "Unknown";
  const char *fullname;
  char *fullnameres = NULL;
  const char *activeres;
  const char *msg = NULL;
  char status;
  char *buf, *buf_locale;

  // Usually we need to update the bottom status line too,
  // at least to refresh the pending message flag.
  scr_update_main_status(FALSE);

  // Clear the line
  werase(chatstatusWnd);

  if (!current_buddy) {
    if (forceupdate) {
      update_panels();
    }
    return;
  }

  fullname = buddy_getname(BUDDATA(current_buddy));
  btype = buddy_gettype(BUDDATA(current_buddy));

  isgrp = ismuc = isspe = 0;
  if (btype & ROSTER_TYPE_USER) {
    btypetext = "Buddy";
  } else if (btype & ROSTER_TYPE_GROUP) {
    btypetext = "Group";
    isgrp = 1;
  } else if (btype & ROSTER_TYPE_AGENT) {
    btypetext = "Agent";
  } else if (btype & ROSTER_TYPE_ROOM) {
    btypetext = "Room";
    ismuc = 1;
  } else if (btype & ROSTER_TYPE_SPECIAL) {
    btypetext = "Special buffer";
    isspe = 1;
  }

  if (chatmode) {
    wprintw(chatstatusWnd, "~");
  } else {
    unsigned short bflags = buddy_getflags(BUDDATA(current_buddy));
    if (bflags & ROSTER_FLAG_MSG) {
      // There is an unread message from the current buddy
      wprintw(chatstatusWnd, "#");
    }
  }

  if (chatmode && !isgrp) {
    winbuf *win_entry;
    win_entry = scr_search_window(buddy_getjid(BUDDATA(current_buddy)), isspe);
    if (win_entry && win_entry->bd->lock)
      mvwprintw(chatstatusWnd, 0, 0, "*");
  }

  if (isgrp || isspe) {
    buf_locale = from_utf8(fullname);
    mvwprintw(chatstatusWnd, 0, 5, "%s: %s", btypetext, buf_locale);
    g_free(buf_locale);
    if (forceupdate) {
      update_panels();
    }
    return;
  }

  status = '?';

  activeres = buddy_getactiveresource(BUDDATA(current_buddy));

  if (ismuc) {
    if (buddy_getinsideroom(BUDDATA(current_buddy)))
      status = 'C';
    else
      status = 'x';
  } else if (xmpp_getstatus() != offline) {
    enum imstatus budstate;
    budstate = buddy_getstatus(BUDDATA(current_buddy), activeres);
    if (budstate < imstatus_size)
      status = imstatus2char[budstate];
  }

  // No status message for MUC rooms
  if (!ismuc) {
    if (activeres) {
      fullnameres = g_strdup_printf("%s/%s", fullname, activeres);
      fullname = fullnameres;
      msg = buddy_getstatusmsg(BUDDATA(current_buddy), activeres);
    } else {
      GSList *resources, *p_res, *p_next_res;
      resources = buddy_getresources(BUDDATA(current_buddy));

      for (p_res = resources ; p_res ; p_res = p_next_res) {
        p_next_res = g_slist_next(p_res);
        // Store the status message of the latest resource (highest priority)
        if (!p_next_res)
          msg = buddy_getstatusmsg(BUDDATA(current_buddy), p_res->data);
        g_free(p_res->data);
      }
      g_slist_free(resources);
    }
  } else {
    msg = buddy_gettopic(BUDDATA(current_buddy));
  }

  if (msg)
    buf = g_strdup_printf("[%c] %s: %s -- %s", status, btypetext, fullname, msg);
  else
    buf = g_strdup_printf("[%c] %s: %s", status, btypetext, fullname);
  replace_nl_with_dots(buf);
  buf_locale = from_utf8(buf);
  mvwprintw(chatstatusWnd, 0, 1, "%s", buf_locale);
  g_free(fullnameres);
  g_free(buf_locale);
  g_free(buf);

  // Display chatstates of the contact, if available.
  if (btype & ROSTER_TYPE_USER) {
    char eventchar = 0;
    guint event;

    // We specify active resource here, so when there is none then the resource
    // with the highest priority will be used.
    event = buddy_resource_getevents(BUDDATA(current_buddy), activeres);

    if (event == ROSTER_EVENT_ACTIVE)
      eventchar = 'A';
    else if (event == ROSTER_EVENT_COMPOSING)
      eventchar = 'C';
    else if (event == ROSTER_EVENT_PAUSED)
      eventchar = 'P';
    else if (event == ROSTER_EVENT_INACTIVE)
      eventchar = 'I';
    else if (event == ROSTER_EVENT_GONE)
      eventchar = 'G';

    if (eventchar)
      mvwprintw(chatstatusWnd, 0, maxX-3, "[%c]", eventchar);
  }


  if (forceupdate) {
    update_panels();
  }
}

void increment_if_buddy_not_filtered(gpointer rosterdata, void *param)
{
  int *p = param;
  if (buddylist_is_status_filtered(buddy_getstatus(rosterdata, NULL)))
    *p=*p+1;
}

//  scr_draw_roster()
// Display the buddylist (not really the roster) on the screen
void scr_draw_roster(void)
{
  static int offset = 0;
  char *name, *rline;
  int maxx, maxy;
  GList *buddy;
  int i, n;
  int rOffset;
  int cursor_backup;
  guint status, pending;
  enum imstatus currentstatus = xmpp_getstatus();
  int x_pos;
  int prefix_length;
  char space[2] = " ";

  // We can reset update_roster
  if (_update_roster == FALSE)
    return;
  _update_roster = FALSE;

  buddylist_build();

  getmaxyx(rosterWnd, maxy, maxx);
  maxx--;  // Last char is for vertical border

  cursor_backup = curs_set(0);

  if (!buddylist)
    offset = 0;
  else
    scr_update_chat_status(FALSE);

  // Cleanup of roster window
  werase(rosterWnd);

  if (Roster_Width) {
    int line_x_pos = roster_win_on_right ? 0 : Roster_Width-1;
    // Redraw the vertical line (not very good...)
    wattrset(rosterWnd, get_color(COLOR_GENERAL));
    for (i=0 ; i < CHAT_WIN_HEIGHT ; i++)
      mvwaddch(rosterWnd, i, line_x_pos, ACS_VLINE);
  }

  // Leave now if buddylist is empty or the roster is hidden
  if (!buddylist || !Roster_Width) {
    update_panels();
    curs_set(cursor_backup);
    return;
  }

  // Update offset if necessary
  // a) Try to show as many buddylist items as possible
  i = g_list_length(buddylist) - maxy;
  if (i < 0)
    i = 0;
  if (i < offset)
    offset = i;
  // b) Make sure the current_buddy is visible
  i = g_list_position(buddylist, current_buddy);
  if (i == -1) { // This is bad
    scr_LogPrint(LPRINT_NORMAL, "Doh! Can't find current selected buddy!!");
    curs_set(cursor_backup);
    return;
  } else if (i < offset) {
    offset = i;
  } else if (i+1 > offset + maxy) {
    offset = i + 1 - maxy;
  }

  if (roster_win_on_right)
    x_pos = 1; // 1 char offset (vertical line)
  else
    x_pos = 0;

  if (roster_no_leading_space) {
    space[0] = '\0';
    prefix_length = 6;
  } else {
    prefix_length = 7;
  }

  name = g_new0(char, 4*Roster_Width);
  rline = g_new0(char, 4*Roster_Width+1);

  buddy = buddylist;
  rOffset = offset;

  for (i=0; i<maxy && buddy; buddy = g_list_next(buddy)) {
    unsigned short bflags, btype;
    unsigned short ismsg, isgrp, ismuc, ishid, isspe;
    guint isurg;
    gchar *rline_locale;

    bflags = buddy_getflags(BUDDATA(buddy));
    btype = buddy_gettype(BUDDATA(buddy));

    ismsg = bflags & ROSTER_FLAG_MSG;
    ishid = bflags & ROSTER_FLAG_HIDE;
    isgrp = btype  & ROSTER_TYPE_GROUP;
    ismuc = btype  & ROSTER_TYPE_ROOM;
    isspe = btype  & ROSTER_TYPE_SPECIAL;
    isurg = buddy_getuiprio(BUDDATA(buddy));

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

    status = '?';
    pending = ' ';

    if (!ismuc) {
      // There is currently no chat state support for MUC
      GSList *resources = buddy_getresources(BUDDATA(buddy));
      GSList *p_res;

      for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
        guint events = buddy_resource_getevents(BUDDATA(buddy),
                                                p_res ? p_res->data : "");
        if ((events & ROSTER_EVENT_PAUSED) && pending != '+')
          pending = '.';
        if (events & ROSTER_EVENT_COMPOSING)
          pending = '+';
        g_free(p_res->data);
      }
      g_slist_free(resources);
    }

    // Display message notice if there is a message flag, but not
    // for unfolded groups.
    if (ismsg && (!isgrp || ishid)) {
      pending = '#';
    }

    if (ismuc) {
      if (buddy_getinsideroom(BUDDATA(buddy)))
        status = 'C';
      else
        status = 'x';
    } else if (currentstatus != offline) {
      enum imstatus budstate;
      budstate = buddy_getstatus(BUDDATA(buddy), NULL);
      if (budstate < imstatus_size)
        status = imstatus2char[budstate];
    }
    if (buddy == current_buddy) {
      if (pending == '#')
        wattrset(rosterWnd, get_color(COLOR_ROSTERSELNMSG));
      else
        wattrset(rosterWnd, get_color(COLOR_ROSTERSEL));
      // The 3 following lines aim at coloring the whole line
      wmove(rosterWnd, i, x_pos);
      for (n = 0; n < maxx; n++)
        waddch(rosterWnd, ' ');
    } else {
      if (pending == '#')
        wattrset(rosterWnd, get_color(COLOR_ROSTERNMSG));
      else {
        int color = get_color(COLOR_ROSTER);
        if ((!isspe) && (!isgrp)) { // Look for color rules
          GSList *head;
          const char *bjid = buddy_getjid(BUDDATA(buddy));
          for (head = rostercolrules; head; head = g_slist_next(head)) {
            rostercolor *rc = head->data;
            if (g_pattern_match_string(rc->compiled, bjid) &&
                (!strcmp("*", rc->status) || strchr(rc->status, status))) {
              color = compose_color(rc->color);
              break;
            }
          }
        }
        wattrset(rosterWnd, color);
      }
    }

    if (Roster_Width > prefix_length)
      g_utf8_strncpy(name, buddy_getname(BUDDATA(buddy)), Roster_Width-prefix_length);
    else
      name[0] = 0;

    if (pending == '#') {
      // Attention sign?
      if ((ismuc && isurg >= ui_attn_sign_prio_level_muc) ||
          (!ismuc && isurg >= ui_attn_sign_prio_level))
        pending = attention_sign();
    }

    if (isgrp) {
      if (ishid) {
        int group_count = 0;
        foreach_group_member(BUDDATA(buddy), increment_if_buddy_not_filtered,
                             &group_count);
        snprintf(rline, 4*Roster_Width, "%s%lc+++ %s (%i)", space, pending,
                 name, group_count);
        /* Do not display the item count if there isn't enough space */
        if (g_utf8_strlen(rline, 4*Roster_Width) >= Roster_Width)
          snprintf(rline, 4*Roster_Width, "%s%lc+++ %s", space, pending, name);
      }
      else
        snprintf(rline, 4*Roster_Width, "%s%lc--- %s", space, pending, name);
    } else if (isspe) {
      snprintf(rline, 4*Roster_Width, "%s%lc%s", space, pending, name);
    } else {
      char sepleft  = '[';
      char sepright = ']';
      if (btype & ROSTER_TYPE_USER) {
        guint subtype = buddy_getsubscription(BUDDATA(buddy));
        if (status == '_' && !(subtype & sub_to))
          status = '?';
        if (!(subtype & sub_from)) {
          sepleft  = '{';
          sepright = '}';
        }
      }
      snprintf(rline, 4*Roster_Width, "%s%lc%c%c%c %s",
               space, pending, sepleft, status, sepright, name);
    }

    rline_locale = from_utf8(rline);
    mvwprintw(rosterWnd, i, x_pos, "%s", rline_locale);
    g_free(rline_locale);
    i++;
  }

  g_free(rline);
  g_free(name);
  top_panel(inputPanel);
  update_panels();
  curs_set(cursor_backup);
}

void scr_update_roster(void)
{
  _update_roster = TRUE;
}


//  scr_roster_visibility(status)
// Set the roster visibility:
// status=1   Show roster
// status=0   Hide roster
// status=-1  Toggle roster status
void scr_roster_visibility(int status)
{
  int old_roster_status = roster_hidden;

  if (status > 0)
    roster_hidden = FALSE;
  else if (status == 0)
    roster_hidden = TRUE;
  else
    roster_hidden = !roster_hidden;

  if (roster_hidden != old_roster_status) {
    // Recalculate windows size and redraw
    scr_resize();
    redrawwin(stdscr);
  }
}

static void scr_write_message(const char *bjid, const char *text,
                              time_t timestamp, guint prefix_flags,
                              unsigned mucnicklen, gpointer xep184)
{
  char *xtext;

  if (!timestamp)
    timestamp = time(NULL);
  else
    prefix_flags |= HBB_PREFIX_DELAYED;

  xtext = ut_expand_tabs(text); // Expand tabs and filter out some chars

  scr_write_in_window(bjid, xtext, timestamp, prefix_flags, FALSE, mucnicklen,
                      xep184);

  if (xtext != (char*)text)
    g_free(xtext);
}

// If prefix is NULL, HBB_PREFIX_IN is supposed.
void scr_write_incoming_message(const char *jidfrom, const char *text,
                                time_t timestamp,
                                guint prefix, unsigned mucnicklen)
{
  if (!(prefix &
        ~HBB_PREFIX_NOFLAG & ~HBB_PREFIX_HLIGHT & ~HBB_PREFIX_HLIGHT_OUT &
        ~HBB_PREFIX_PGPCRYPT & ~HBB_PREFIX_OTRCRYPT & ~HBB_PREFIX_CARBON))
    prefix |= HBB_PREFIX_IN;

  scr_write_message(jidfrom, text, timestamp, prefix, mucnicklen, NULL);
}

void scr_write_outgoing_message(const char *jidto, const char *text,
                                guint prefix, gpointer xep184)
{
  GSList *roster_elt;
  roster_elt = roster_find(jidto, jidsearch,
                           ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);

  scr_write_message(jidto, text,
                    0, prefix|HBB_PREFIX_OUT|HBB_PREFIX_HLIGHT_OUT, 0, xep184);

  // Show jidto's buffer unless the buddy is not in the buddylist
  if (roster_elt && g_list_position(buddylist, roster_elt->data) != -1)
    scr_show_window(jidto, FALSE);
}

void scr_remove_receipt_flag(const char *bjid, gconstpointer xep184)
{
  winbuf *win_entry = scr_search_window(bjid, FALSE);
  if (win_entry && xep184) {
    hbuf_remove_receipt(win_entry->bd->hbuf, xep184);
    if (chatmode && (buddy_search_jid(bjid) == current_buddy))
      scr_update_buddy_window();
  }
}

static inline void set_autoaway(bool setaway)
{
  static enum imstatus oldstatus;
  static char *oldmsg;
  Autoaway = setaway;

  if (setaway) {
    const char *msg, *prevmsg;
    oldstatus = xmpp_getstatus();
    if (oldmsg) {
      g_free(oldmsg);
      oldmsg = NULL;
    }
    prevmsg = xmpp_getstatusmsg();
    msg = settings_opt_get("message_autoaway");
    if (!msg)
      msg = prevmsg;
    if (prevmsg)
      oldmsg = g_strdup(prevmsg);
    xmpp_setstatus(away, NULL, msg, FALSE);
  } else {
    // Back
    xmpp_setstatus(oldstatus, NULL, (oldmsg ? oldmsg : ""), FALSE);
    if (oldmsg) {
      g_free(oldmsg);
      oldmsg = NULL;
    }
  }
}

//  set_chatstate(state)
// Set the current chat state (0=active, 1=composing, 2=paused)
// If the chat state has changed, call xmpp_send_chatstate()
static void set_chatstate(int state)
{
#ifdef XEP0085
  if (chatstates_disabled)
    return;
  if (!chatmode)
    state = 0;
  if (state != chatstate) {
    chatstate = state;
    if (current_buddy &&
        buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_USER) {
      guint jep_state;
      if (chatstate == 1) {
        if (chatstate_timeout_id == 0)
          chatstate_timeout_id = g_timeout_add_seconds(1,
                                                       scr_chatstates_timeout,
                                                       NULL);
        jep_state = ROSTER_EVENT_COMPOSING;
      }
      else if (chatstate == 2)
        jep_state = ROSTER_EVENT_PAUSED;
      else
        jep_state = ROSTER_EVENT_ACTIVE;
      xmpp_send_chatstate(BUDDATA(current_buddy), jep_state);
    }
    if (!chatstate)
      chatstate_timestamp = 0;
  }
#endif
}

#ifdef XEP0085
static gboolean scr_chatstates_timeout(void)
{
  time_t now;
  time(&now);
  // Check if we're currently composing...
  if (chatstate != 1 || !chatstate_timestamp) {
    chatstate_timeout_id = 0;
    return FALSE;
  }

  // If the timeout is reached, let's change the state right now.
  if (now >= chatstate_timestamp + COMPOSING_TIMEOUT) {
    chatstate_timestamp = now;
    set_chatstate(2);
    chatstate_timeout_id = 0;
    return FALSE;
  }
  return TRUE;
}
#endif

static gboolean scr_autoaway_timeout_callback(gpointer data)
{
  enum imstatus cur_st = xmpp_getstatus();
  if (cur_st != available && cur_st != freeforchat)
    // Some non-user-originated status changes, let's wait more.
    // Maybe the proper fix for that will be set global variable
    // "autoaway_delayed" and check that variable in postconnect
    // hook (afaik, only source for such status changes are
    // error disconnects).
    return TRUE;
  set_autoaway(TRUE);
  // source will be destroyed after return
  autoaway_source = 0;
  return FALSE;
}

static void scr_reinstall_autoaway_timeout(void)
{
  unsigned int autoaway_timeout = settings_opt_get_int("autoaway");
  enum imstatus cur_st = xmpp_getstatus();
  if (autoaway_source) {
    g_source_remove(autoaway_source);
    autoaway_source = 0;
  }
  if (autoaway_timeout && (cur_st == available || cur_st == freeforchat))
    autoaway_source = g_timeout_add_seconds(autoaway_timeout,
                                            scr_autoaway_timeout_callback,
                                            NULL);
}

// Check if we should reset autoaway timeout source
void scr_check_auto_away(int activity)
{
  if (Autoaway && activity) {
    scr_reinstall_autoaway_timeout();
    set_autoaway(FALSE);
  } else if (activity || !autoaway_source)
    scr_reinstall_autoaway_timeout();
}

//  set_current_buddy(newbuddy)
// Set the current_buddy to newbuddy (if not NULL)
// Lock the newbuddy, and unlock the previous current_buddy
static void set_current_buddy(GList *newbuddy)
{
  enum imstatus prev_st = imstatus_size;
  /* prev_st initialized to imstatus_size, which is used as "undef" value.
   * We are sure prev_st will get a different status value after the
   * buddy_getstatus() call.
   */

  if (!current_buddy || !newbuddy)  return;
  if (newbuddy == current_buddy)    return;

  // We're moving to another buddy.  We're thus inactive wrt current_buddy.
  set_chatstate(0);
  // We don't want the chatstate to be changed again right now.
  lock_chatstate = TRUE;

  prev_st = buddy_getstatus(BUDDATA(current_buddy), NULL);
  buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
  if (chatmode) {
    scr_buffer_readmark(TRUE);
    alternate_buddy = current_buddy;
  }
  current_buddy = newbuddy;
  // Lock the buddy in the buddylist if we're in chat mode
  if (chatmode) {
    buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, TRUE);
    // Remove the readmark if it is at the end of the buffer
    scr_buffer_readmark(-1);
  }
  // We should rebuild the buddylist when the last selected buddy isn't
  // displayed anymore
  if (!(buddylist_get_filter() & 1<<prev_st))
    buddylist_defer_build();
  scr_update_roster();
}

//  scr_roster_top()
// Go to the first buddy in the buddylist
void scr_roster_top(void)
{
  set_current_buddy(buddylist);
  if (chatmode) {
    last_activity_buddy = current_buddy;
    scr_show_buddy_window();
  }
}

//  scr_roster_bottom()
// Go to the last buddy in the buddylist
void scr_roster_bottom(void)
{
  set_current_buddy(g_list_last(buddylist));
  if (chatmode) {
    last_activity_buddy = current_buddy;
    scr_show_buddy_window();
  }
}

//  scr_roster_up_down(updown, n)
// Go to the nth next buddy in the buddylist
// (up if updown == -1, down if updown == 1)
void scr_roster_up_down(int updown, unsigned int n)
{
  unsigned int i;
  GList *new_buddy = current_buddy;
  GList *tmp_buddy;

  if (!current_buddy)
    return;

  for (i = 0; i < n; i++) {
    if (updown < 0)
      tmp_buddy = g_list_previous(new_buddy);
    else
      tmp_buddy = g_list_next(new_buddy);
    if (tmp_buddy)
      new_buddy = tmp_buddy;
  }
  if (new_buddy == current_buddy)
    return;

  set_current_buddy(new_buddy);
  if (chatmode) {
    last_activity_buddy = current_buddy;
    scr_show_buddy_window();
  }
}

//  scr_roster_prev_group()
// Go to the previous group in the buddylist
void scr_roster_prev_group(void)
{
  GList *bud;

  for (bud = current_buddy ; bud ; ) {
    bud = g_list_previous(bud);
    if (!bud)
      break;
    if (buddy_gettype(BUDDATA(bud)) & ROSTER_TYPE_GROUP) {
      set_current_buddy(bud);
      if (chatmode) {
        last_activity_buddy = current_buddy;
        scr_show_buddy_window();
      }
      break;
    }
  }
}

//  scr_roster_next_group()
// Go to the next group in the buddylist
void scr_roster_next_group(void)
{
  GList *bud;

  for (bud = current_buddy ; bud ; ) {
    bud = g_list_next(bud);
    if (!bud)
      break;
    if (buddy_gettype(BUDDATA(bud)) & ROSTER_TYPE_GROUP) {
      set_current_buddy(bud);
      if (chatmode) {
        last_activity_buddy = current_buddy;
        scr_show_buddy_window();
      }
      break;
    }
  }
}

//  scr_roster_search(str)
// Look forward for a buddy with jid/name containing str.
void scr_roster_search(char *str)
{
  set_current_buddy(buddy_search(str));
  if (chatmode) {
    last_activity_buddy = current_buddy;
    scr_show_buddy_window();
  }
}

//  scr_roster_jump_jid(bjid)
// Jump to buddy bjid.
// NOTE: With this function, the buddy is added to the roster if doesn't exist.
void scr_roster_jump_jid(char *barejid)
{
  GSList *roster_elt;
  // Look for an existing buddy
  roster_elt = roster_find(barejid, jidsearch,
                 ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
  // Create it if necessary
  if (!roster_elt)
    roster_elt = roster_add_user(barejid, NULL, NULL, ROSTER_TYPE_USER,
                                 sub_none, -1);
  // Set a lock to see it in the buddylist
  buddy_setflags(BUDDATA(roster_elt), ROSTER_FLAG_LOCK, TRUE);
  buddylist_defer_build();
  // Jump to the buddy
  set_current_buddy(buddy_search_jid(barejid));
  if (chatmode) {
    last_activity_buddy = current_buddy;
    scr_show_buddy_window();
  }
}

//  scr_roster_unread_message(next)
// Go to a new message.  If next is not null, try to go to the next new
// message.  If it is not possible or if next is NULL, go to the first new
// message from unread_list.
void scr_roster_unread_message(int next)
{
  gpointer unread_ptr;
  gpointer refbuddata;
  GList *nbuddy;

  if (!current_buddy) return;

  if (next) refbuddata = BUDDATA(current_buddy);
  else      refbuddata = NULL;

  unread_ptr = unread_msg(refbuddata);
  if (!unread_ptr) {
    if (!last_activity_buddy || g_list_position(buddylist, last_activity_buddy) == -1)
      return;
    unread_ptr = BUDDATA(last_activity_buddy);
  }

  if (!(buddy_gettype(unread_ptr) & ROSTER_TYPE_SPECIAL)) {
    gpointer ngroup;
    // If buddy is in a folded group, we need to expand it
    ngroup = buddy_getgroup(unread_ptr);
    if (buddy_getflags(ngroup) & ROSTER_FLAG_HIDE) {
      buddy_setflags(ngroup, ROSTER_FLAG_HIDE, FALSE);
      buddylist_defer_build();
    }
  }

  nbuddy = g_list_find(buddylist, unread_ptr);
  if (nbuddy) {
    set_current_buddy(nbuddy);
    if (chatmode) scr_show_buddy_window();
  } else
    scr_LogPrint(LPRINT_LOGNORM, "Error: nbuddy == NULL"); // should not happen
}

//  scr_roster_next_open_buffer()
// Jump to the next open buffer (experimental XXX)
// This implementation ignores the hidden entries (folded groups).
void scr_roster_next_open_buffer(void)
{
  GList *bud = current_buddy;

  if (!current_buddy) return;

  for (;;) {
    guint budtype;
    bud = g_list_next(bud);
    // End of list: jump to the first entry
    if (!bud)
      bud = buddylist;
    // Check if we're back to the initial position
    if (bud == current_buddy)
      break;
    // Ignore the special buffer(s), groups
    budtype = buddy_gettype(BUDDATA(bud));
    if (budtype & (ROSTER_TYPE_GROUP | ROSTER_TYPE_SPECIAL))
      continue;

    // Check if a buffer/window exists
    if (scr_search_window(buddy_getjid(BUDDATA(bud)), 0)) {
      set_current_buddy(bud);
      if (chatmode) {
        last_activity_buddy = current_buddy;
        scr_show_buddy_window();
      }
      break;
    }
  }
}

//  scr_roster_jump_alternate()
// Try to jump to alternate (== previous) buddy
void scr_roster_jump_alternate(void)
{
  if (!alternate_buddy || g_list_position(buddylist, alternate_buddy) == -1)
    return;
  set_current_buddy(alternate_buddy);
  if (chatmode) {
    last_activity_buddy = current_buddy;
    scr_show_buddy_window();
  }
}

//  scr_roster_display(filter)
// Set the roster filter mask.  If filter is null/empty, the current
// mask is displayed.
void scr_roster_display(const char *filter)
{
  guchar status;
  enum imstatus budstate;
  char strfilter[imstatus_size+1];
  char *psfilter;

  if (filter && *filter) {
    int show_all = (*filter == '*');
    status = 0;
    for (budstate = 0; budstate < imstatus_size-1; budstate++)
      if (strchr(filter, imstatus2char[budstate]) || show_all)
        status |= 1<<budstate;
    buddylist_set_filter(status);
    buddylist_defer_build();
    scr_update_roster();
    return;
  }

  // Display current filter
  psfilter = strfilter;
  status = buddylist_get_filter();
  for (budstate = 0; budstate < imstatus_size-1; budstate++)
    if (status & 1<<budstate)
      *psfilter++ = imstatus2char[budstate];
  *psfilter = '\0';
  scr_LogPrint(LPRINT_NORMAL, "Roster status filter: %s", strfilter);
}

//  scr_buffer_scroll_up_down()
// Scroll up/down the current buddy window,
// - half a screen if nblines is 0,
// - up if updown == -1, down if updown == 1
void scr_buffer_scroll_up_down(int updown, unsigned int nblines)
{
  winbuf *win_entry;
  int n, nbl;
  GList *hbuf_top;
  guint isspe;

  // Get win_entry
  if (!current_buddy) return;

  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
  win_entry  = scr_search_window(CURRENT_JID, isspe);
  if (!win_entry) return;

  if (!nblines) {
    // Scroll half a screen (or less)
    nbl = CHAT_WIN_HEIGHT/2;
  } else {
    nbl = nblines;
  }
  hbuf_top = win_entry->bd->top;

  if (updown == -1) {   // UP
    n = 0;
    if (!hbuf_top) {
      hbuf_top = g_list_last(win_entry->bd->hbuf);
      if (!win_entry->bd->cleared) {
        if (!nblines) nbl = nbl*3 - 1;
        else nbl += CHAT_WIN_HEIGHT - 1;
      } else {
        win_entry->bd->cleared = FALSE;
        n++; // We'll scroll one line less
      }
    }
    for ( ; hbuf_top && n < nbl && g_list_previous(hbuf_top) ; n++)
      hbuf_top = g_list_previous(hbuf_top);
    win_entry->bd->top = hbuf_top;
  } else {              // DOWN
    for (n=0 ; hbuf_top && n < nbl ; n++)
      hbuf_top = g_list_next(hbuf_top);
    win_entry->bd->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->bd->top = NULL; // End reached
  }

  // Refresh the window
  scr_update_window(win_entry);

  // Finished :)
  update_panels();
}

//  scr_buffer_clear()
// Clear the current buddy window (used for the /clear command)
void scr_buffer_clear(void)
{
  winbuf *win_entry;
  guint isspe;

  // Get win_entry
  if (!current_buddy) return;
  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
  win_entry = scr_search_window(CURRENT_JID, isspe);
  if (!win_entry) return;

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

  // Refresh the window
  scr_update_window(win_entry);

  // Finished :)
  update_panels();
}

//  buffer_purge()
// key: winId/jid
// value: winbuf structure
// data: int, set to 1 if the buffer should be closed.
// NOTE: does not work for special buffers.
// Returns TRUE IFF the win_entry can be closed and freed.
static gboolean buffer_purge(gpointer key, gpointer value, gpointer data)
{
  int *p_closebuf = data;
  winbuf *win_entry = value;
  gboolean retval = FALSE;

  // Delete the current hbuf
  // unless we close the buffer *and* this is a shared bd structure
  if (!(*p_closebuf && win_entry->bd->refcount))
    hbuf_free(&win_entry->bd->hbuf);

  if (*p_closebuf) {
    GSList *roster_elt;
    retval = TRUE;
    roster_elt = roster_find(key, jidsearch,
        ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
    if (roster_elt)
      buddy_setactiveresource(roster_elt->data, NULL);
    if (win_entry->bd->refcount) {
      win_entry->bd->refcount--;
    } else {
      g_free(win_entry->bd);
      win_entry->bd = NULL;
    }
  } else {
    win_entry->bd->cleared = FALSE;
    win_entry->bd->top = NULL;
  }
  return retval;
}

//  scr_buffer_purge(closebuf, jid)
// Purge/Drop the current buddy buffer or jid's buffer if jid != NULL.
// If closebuf is 1, close the buffer.
void scr_buffer_purge(int closebuf, const char *jid)
{
  winbuf *win_entry;
  guint isspe;
  const char *cjid;
  char *ljid = NULL;
  guint hold_chatmode = FALSE;

  if (jid) {
    isspe = FALSE;
    ljid = g_strdup(jid);
    mc_strtolower(ljid);
    cjid = ljid;
    // If closebuf is TRUE, it's probably better not to leave chat mode
    // if the change isn't related to the current buffer.
    if (closebuf && current_buddy) {
      if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL ||
          strcasecmp(jid, CURRENT_JID))
        hold_chatmode = TRUE;
    }
  } else {
    // Get win_entry
    if (!current_buddy) return;
    cjid = CURRENT_JID;
    isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
  }
  win_entry = scr_search_window(cjid, isspe);
  if (!win_entry) {
    g_free(ljid);
    return;
  }

  if (!isspe) {
    if (buffer_purge((gpointer)cjid, win_entry, &closebuf))
      g_hash_table_remove(winbufhash, cjid);
    roster_msg_setflag(cjid, FALSE, FALSE);
    if (closebuf && !hold_chatmode) {
      scr_set_chatmode(FALSE);
      currentWindow = NULL;
    }
  } else {
    // (Special buffer)
    // Reset the current hbuf
    hbuf_free(&win_entry->bd->hbuf);
    // Currently it can only be the status buffer
    statushbuf = NULL;
    roster_msg_setflag(SPECIAL_BUFFER_STATUS_ID, TRUE, FALSE);

    win_entry->bd->cleared = FALSE;
    win_entry->bd->top = NULL;
  }

  scr_update_roster();

  // Refresh the window
  scr_update_buddy_window();

  // Finished :)
  update_panels();

  g_free(ljid);
}

//  scr_buffer_purge_all(closebuf)
// Purge all existing buffers.
// If closebuf is 1, the buffers are closed.
void scr_buffer_purge_all(int closebuf)
{
  g_hash_table_foreach_remove(winbufhash, buffer_purge, &closebuf);

  if (closebuf) {
    scr_set_chatmode(FALSE);
    currentWindow = NULL;
  }

  // Refresh the window
  scr_update_buddy_window();

  // Finished :)
  update_panels();
}

//  scr_buffer_scroll_lock(lock)
// Lock/unlock the current buddy buffer
// lock = 1 : lock
// lock = 0 : unlock
// lock = -1: toggle lock status
void scr_buffer_scroll_lock(int lock)
{
  winbuf *win_entry;
  guint isspe;

  // Get win_entry
  if (!current_buddy) return;
  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
  win_entry = scr_search_window(CURRENT_JID, isspe);
  if (!win_entry) return;

  if (lock == -1)
    lock = !win_entry->bd->lock;

  if (lock) {
    win_entry->bd->lock = TRUE;
  } else {
    win_entry->bd->lock = FALSE;
    if (isspe || (buddy_getflags(BUDDATA(current_buddy)) & ROSTER_FLAG_MSG))
      win_entry->bd->top = NULL;
  }

  // If chatmode is disabled and we're at the bottom of the buffer,
  // we need to set the "top" line, so we need to call scr_show_buddy_window()
  // at least once.  (Maybe it will cause a double refresh...)
  if (!chatmode && !win_entry->bd->top) {
    chatmode = TRUE;
    scr_show_buddy_window();
    chatmode = FALSE;
  }

  // Refresh the window
  scr_update_buddy_window();

  // Finished :)
  update_panels();
}

//  scr_buffer_readmark(action)
// Update the readmark flag for the current buffer
// If action = 1, set the readmark flag on the last message
// If action = 0, reset the readmark flag
// If action = -1, remove the readmark flag iff it is on the last line
void scr_buffer_readmark(gchar action)
{
  winbuf *win_entry;
  guint isspe;
  int autolock;

  // Get win_entry
  if (!current_buddy) return;
  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
  if (isspe) return; // Maybe not necessary
  win_entry = scr_search_window(CURRENT_JID, isspe);
  if (!win_entry) return;

  autolock = settings_opt_get_int("buffer_smart_scrolling");
  if (!win_entry->bd->lock || autolock) {
    if (action >= 0)
      hbuf_set_readmark(win_entry->bd->hbuf, action);
    else
      hbuf_remove_trailing_readmark(win_entry->bd->hbuf);
  }
}


//  scr_buffer_top_bottom()
// Jump to the head/tail of the current buddy window
// (top if topbottom == -1, bottom topbottom == 1)
void scr_buffer_top_bottom(int topbottom)
{
  winbuf *win_entry;
  guint isspe;

  // Get win_entry
  if (!current_buddy) return;
  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
  win_entry = scr_search_window(CURRENT_JID, isspe);
  if (!win_entry) return;

  win_entry->bd->cleared = FALSE;
  if (topbottom == 1)
    win_entry->bd->top = NULL;
  else
    win_entry->bd->top = g_list_first(win_entry->bd->hbuf);

  // Refresh the window
  scr_update_window(win_entry);

  // Finished :)
  update_panels();
}

//  scr_buffer_search(direction, text)
// Jump to the next line containing text
// (backward search if direction == -1, forward if topbottom == 1)
void scr_buffer_search(int direction, const char *text)
{
  winbuf *win_entry;
  GList *current_line, *search_res;
  guint isspe;

  // Get win_entry
  if (!current_buddy) return;
  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
  win_entry = scr_search_window(CURRENT_JID, isspe);
  if (!win_entry) return;

  if (win_entry->bd->top)
    current_line = win_entry->bd->top;
  else
    current_line = g_list_last(win_entry->bd->hbuf);

  search_res = hbuf_search(current_line, direction, text);

  if (search_res) {
    win_entry->bd->cleared = FALSE;
    win_entry->bd->top = search_res;

    // Refresh the window
    scr_update_window(win_entry);

    // Finished :)
    update_panels();
  } else
    scr_LogPrint(LPRINT_NORMAL, "Search string not found.");
}

//  scr_buffer_percent(n)
// Jump to the specified position in the buffer, in %
void scr_buffer_percent(int pc)
{
  winbuf *win_entry;
  GList *search_res;
  guint isspe;

  // Get win_entry
  if (!current_buddy) return;
  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
  win_entry = scr_search_window(CURRENT_JID, isspe);
  if (!win_entry) return;

  if (pc < 0 || pc > 100) {
    scr_LogPrint(LPRINT_NORMAL, "Bad %% value");
    return;
  }

  search_res = hbuf_jump_percent(win_entry->bd->hbuf, pc);

  win_entry->bd->cleared = FALSE;
  win_entry->bd->top = search_res;

  // Refresh the window
  scr_update_window(win_entry);

  // Finished :)
  update_panels();
}

//  scr_buffer_date(t)
// Jump to the first line after date t in the buffer
// t is a date in seconds since `00:00:00 1970-01-01 UTC'
void scr_buffer_date(time_t t)
{
  winbuf *win_entry;
  GList *search_res;
  guint isspe;

  // Get win_entry
  if (!current_buddy) return;
  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
  win_entry = scr_search_window(CURRENT_JID, isspe);
  if (!win_entry) return;

  search_res = hbuf_jump_date(win_entry->bd->hbuf, t);

  win_entry->bd->cleared = FALSE;
  win_entry->bd->top = search_res;

  if (!search_res)
    scr_log_print(LPRINT_NORMAL, "Date not found.");

  // Refresh the window
  scr_update_window(win_entry);

  // Finished :)
  update_panels();
}

//  scr_buffer_jump_readmark()
// Jump to the buffer readmark, if there's one
void scr_buffer_jump_readmark(void)
{
  winbuf *win_entry;
  GList *search_res;
  guint isspe;

  // Get win_entry
  if (!current_buddy) return;
  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
  if (isspe) return;
  win_entry = scr_search_window(CURRENT_JID, isspe);
  if (!win_entry) return;

  search_res = hbuf_jump_readmark(win_entry->bd->hbuf);

  if (!search_res) {
    scr_log_print(LPRINT_NORMAL, "Readmark not found.");
    return;
  }

  win_entry->bd->cleared = FALSE;
  win_entry->bd->top = search_res;

  // Refresh the window
  scr_update_window(win_entry);

  // Finished :)
  update_panels();
}

//  scr_buffer_dump(filename)
// Dump the current buffer content to the specified file.
void scr_buffer_dump(const char *file)
{
  char *extfname;

  if (!currentWindow) {
    scr_LogPrint(LPRINT_NORMAL, "No current buffer!");
    return;
  }

  if (!file || !*file) {
    scr_LogPrint(LPRINT_NORMAL, "Missing parameter (file name)!");
    return;
  }

  extfname = expand_filename(file);
  hbuf_dump_to_file(currentWindow->bd->hbuf, extfname);
  g_free(extfname);
}

//  buffer_list()
// key: winId/jid
// value: winbuf structure
// data: none.
static void buffer_list(gpointer key, gpointer value, gpointer data)
{
  GList *head;
  winbuf *win_entry = value;

  head = g_list_first(win_entry->bd->hbuf);

  scr_LogPrint(LPRINT_NORMAL, " %s  (%u/%u)", (const char *) key,
               g_list_length(head), hbuf_get_blocks_number(head));
}

void scr_buffer_list(void)
{
  scr_LogPrint(LPRINT_NORMAL, "Buffer list:");
  buffer_list("[status]", statusWindow, NULL);
  g_hash_table_foreach(winbufhash, buffer_list, NULL);
  scr_LogPrint(LPRINT_NORMAL, "End of buffer list.");
  scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
  scr_setattentionflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE,
                                 ROSTER_UI_PRIO_STATUS_WIN_MESSAGE, prio_max);
}

//  scr_set_chatmode()
// Public function to (un)set chatmode...
inline void scr_set_chatmode(int enable)
{
  gboolean enter_chatmode = enable && chatmode == FALSE;
  chatmode = enable;
  scr_update_chat_status(TRUE);
  if (enter_chatmode)
    scr_buffer_readmark(-1);
}

//  scr_get_chatmode()
// Public function to get chatmode state.
inline int scr_get_chatmode(void)
{
  return chatmode;
}

//  scr_get_multimode()
// Public function to get multimode status...
inline int scr_get_multimode(void)
{
  return multimode;
}

//  scr_setmsgflag_if_needed(jid)
// Set the message flag unless we're already in the jid buffer window
void scr_setmsgflag_if_needed(const char *bjid, int special)
{
  const char *current_id;
  bool iscurrentlocked = FALSE;

  if (!bjid)
    return;

  if (current_buddy) {
    if (special)
      current_id = buddy_getname(BUDDATA(current_buddy));
    else
      current_id = buddy_getjid(BUDDATA(current_buddy));
    if (current_id) {
      winbuf *win_entry = scr_search_window(current_id, special);
      if (!win_entry) return;
      iscurrentlocked = win_entry->bd->lock;
    }
  } else {
    current_id = NULL;
  }
  if (!chatmode || !current_id || strcmp(bjid, current_id) || iscurrentlocked) {
    roster_msg_setflag(bjid, special, TRUE);
    scr_update_roster();
  }
}

//  scr_setattentionflag_if_needed(bare_jid, special, value, action)
// Set the attention flag unless we're already in the jid buffer window
// TODO: avoid code duplication with scr_setmsgflag_if_needed()
void scr_setattentionflag_if_needed(const char *bjid, int special,
                                    guint value, enum setuiprio_ops action)
{
  const char *current_id;
  winbuf *wb;
  bool iscurrentlocked = FALSE;

  if (!bjid)
    return;

  wb = scr_search_window(bjid, special);
  if (!wb)
    return;

  if (current_buddy) {
    if (special)
      current_id = buddy_getname(BUDDATA(current_buddy));
    else
      current_id = buddy_getjid(BUDDATA(current_buddy));
    if (current_id) {
      winbuf *win_entry = scr_search_window(current_id, special);
      if (!win_entry) return;
      iscurrentlocked = win_entry->bd->lock;
    }
  } else {
    current_id = NULL;
  }

  if (!chatmode || !current_id || strcmp(bjid, current_id) || iscurrentlocked) {
    roster_setuiprio(bjid, special, value, action);
    scr_update_roster();
  }
}

//  scr_set_multimode()
// Public function to (un)set multimode...
// Convention:
//  0 = disabled / 1 = multimode / 2 = multimode verbatim (commands disabled)
void scr_set_multimode(int enable, char *subject)
{
  g_free(multiline);
  multiline = NULL;

  g_free(multimode_subj);
  if (enable && subject)
    multimode_subj = g_strdup(subject);
  else
    multimode_subj = NULL;

  multimode = enable;
}

//  scr_get_multiline()
// Public function to get the current multi-line.
const char *scr_get_multiline(void)
{
  if (multimode && multiline)
    return multiline;
  return NULL;
}

//  scr_get_multimode_subj()
// Public function to get the multi-line subject, if any.
const char *scr_get_multimode_subj(void)
{
  if (multimode)
    return multimode_subj;
  return NULL;
}

//  scr_append_multiline(line)
// Public function to append a line to the current multi-line message.
// Skip empty leading lines.
void scr_append_multiline(const char *line)
{
  static int num;

  if (!multimode) {
    scr_LogPrint(LPRINT_NORMAL, "Error: Not in multi-line message mode!");
    return;
  }
  if (multiline) {
    int len = strlen(multiline)+strlen(line)+2;
    if (len >= HBB_BLOCKSIZE - 1) {
      // We don't handle single messages with size > HBB_BLOCKSIZE
      // (see hbuf)
      scr_LogPrint(LPRINT_NORMAL, "Your multi-line message is too big, "
                   "this line has not been added.");
      scr_LogPrint(LPRINT_NORMAL, "Please send this part now...");
      return;
    }
    if (num >= MULTILINE_MAX_LINE_NUMBER) {
      // We don't allow too many lines; however the maximum is arbitrary
      // (It should be < 1000 yet)
      scr_LogPrint(LPRINT_NORMAL, "Your message has too many lines, "
                   "this one has not been added.");
      scr_LogPrint(LPRINT_NORMAL, "Please send this part now...");
      return;
    }
    multiline = g_renew(char, multiline, len);
    strcat(multiline, "\n");
    strcat(multiline, line);
    num++;
  } else {
    // First message line (we skip leading empty lines)
    num = 0;
    if (line[0]) {
      multiline = g_strdup(line);
      num++;
    } else
      return;
  }
  scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
               "Multi-line mode: line #%d added  [%.25s...", num, line);
}

//  scr_cmdhisto_addline()
// Add a line to the inputLine history
static void scr_cmdhisto_addline(char *line)
{
  int max_histo_lines;

  if (!line || !*line)
    return;

  max_histo_lines = settings_opt_get_int("cmdhistory_lines");

  if (max_histo_lines < 0)
    max_histo_lines = 1;

  if (max_histo_lines)
    while (cmdhisto_nblines >= (guint)max_histo_lines) {
      if (cmdhisto_cur && cmdhisto_cur == cmdhisto)
        break;
      g_free(cmdhisto->data);
      cmdhisto = g_list_delete_link(cmdhisto, cmdhisto);
      cmdhisto_nblines--;
    }

  cmdhisto = g_list_append(cmdhisto, g_strdup(line));
  cmdhisto_nblines++;
}

//  scr_cmdhisto_reset()
// Reset the inputLine history
static void scr_cmdhisto_reset(void)
{
  while (cmdhisto_nblines) {
    g_free(cmdhisto->data);
    cmdhisto = g_list_delete_link(cmdhisto, cmdhisto);
    cmdhisto_nblines--;
  }
  cmdhisto_backup[0] = 0;
  cmdhisto_cur = NULL;
}

//  scr_cmdhisto_prev()
// Look for previous line beginning w/ the given mask in the inputLine history
// Returns NULL if none found
static const char *scr_cmdhisto_prev(char *mask, guint len)
{
  GList *hl;
  if (!cmdhisto_cur) {
    hl = g_list_last(cmdhisto);
    if (hl) { // backup current line
      strncpy(cmdhisto_backup, mask, INPUTLINE_LENGTH);
    }
  } else {
    hl = g_list_previous(cmdhisto_cur);
  }
  while (hl) {
    if (!strncmp((char*)hl->data, mask, len)) {
      // Found a match
      cmdhisto_cur = hl;
      return (const char*)hl->data;
    }
    hl = g_list_previous(hl);
  }
  return NULL;
}

//  scr_cmdhisto_next()
// Look for next line beginning w/ the given mask in the inputLine history
// Returns NULL if none found
static const char *scr_cmdhisto_next(char *mask, guint len)
{
  GList *hl;
  if (!cmdhisto_cur) return NULL;
  hl = cmdhisto_cur;
  while ((hl = g_list_next(hl)) != NULL)
    if (!strncmp((char*)hl->data, mask, len)) {
      // Found a match
      cmdhisto_cur = hl;
      return (const char*)hl->data;
    }
  // If the "backuped" line matches, we'll use it
  if (strncmp(cmdhisto_backup, mask, len)) return NULL; // No match
  cmdhisto_cur = NULL;
  return cmdhisto_backup;
}

static char *_strmove(char *dst, const char *src)
{
  char *dest = dst;
  while ((*dest++ = *src++) != '\0')
    ;
  return dest;
}

//  readline_transpose_chars()
// Drag  the  character  before point forward over the character at
// point, moving point forward as well.  If point is at the end  of
// the  line, then this transposes the two characters before point.
void readline_transpose_chars(void)
{
  char *c1, *c2;
  unsigned a, b;

  if (ptr_inputline == inputLine) return;

  if (!*ptr_inputline) { // We're at EOL
    // If line is only 1 char long, nothing to do...
    if (ptr_inputline == prev_char(ptr_inputline, inputLine)) return;
    // Transpose the two previous characters
    c2 = prev_char(ptr_inputline, inputLine);
    c1 = prev_char(c2, inputLine);
    a = get_char(c1);
    b = get_char(c2);
    put_char(put_char(c1, b), a);
  } else {
    // Swap the two characters before the cursor and move right.
    c2 = ptr_inputline;
    c1 = prev_char(c2, inputLine);
    a = get_char(c1);
    b = get_char(c2);
    put_char(put_char(c1, b), a);
    check_offset(1);
  }
}

void readline_forward_kill_word(void)
{
  char *c, *old = ptr_inputline;
  int spaceallowed = 1;

  if (! *ptr_inputline) return;

  for (c = ptr_inputline ; *c ; c = next_char(c)) {
    if (!iswalnum(get_char(c))) {
      if (iswblank(get_char(c))) {
        if (!spaceallowed) break;
      } else spaceallowed = 0;
    } else spaceallowed = 0;
  }

  // Modify the line
  for (;;) {
    *old = *c++;
    if (!*old++) break;
  }
}

//  readline_backward_kill_word()
// Kill the word before the cursor, in input line
void readline_backward_kill_word(void)
{
  char *c, *old = ptr_inputline;
  int spaceallowed = 1;

  if (ptr_inputline == inputLine) return;

  c = prev_char(ptr_inputline, inputLine);
  for ( ; c > inputLine ; c = prev_char(c, inputLine)) {
    if (!iswalnum(get_char(c))) {
      if (iswblank(get_char(c))) {
        if (!spaceallowed) break;
      } else spaceallowed = 0;
    } else spaceallowed = 0;
  }

  if (c == inputLine && *c == COMMAND_CHAR && old != c+1) {
      c = next_char(c);
  } else if (c != inputLine || (iswblank(get_char(c)) && !spaceallowed)) {
    if ((c < prev_char(ptr_inputline, inputLine)) && (!iswalnum(get_char(c))))
      c = next_char(c);
  }

  // Modify the line
  ptr_inputline = c;
  _strmove(ptr_inputline, old);
  check_offset(-1);
}

//  readline_backward_word()
// Move back to the start of the current or previous word
void readline_backward_word(void)
{
  int i = 0;

  if (ptr_inputline == inputLine) return;

  if (iswalnum(get_char(ptr_inputline)) &&
      !iswalnum(get_char(prev_char(ptr_inputline, inputLine))))
    i--;

  for ( ;
       ptr_inputline > inputLine;
       ptr_inputline = prev_char(ptr_inputline, inputLine)) {
    if (!iswalnum(get_char(ptr_inputline))) {
      if (i) {
        ptr_inputline = next_char(ptr_inputline);
        break;
      }
    } else i++;
  }

  check_offset(-1);
}

//  readline_forward_word()
// Move forward to the end of the next word
void readline_forward_word(void)
{
  int stopsymbol_allowed = 1;

  while (*ptr_inputline) {
    if (!iswalnum(get_char(ptr_inputline))) {
      if (!stopsymbol_allowed) break;
    } else stopsymbol_allowed = 0;
    ptr_inputline = next_char(ptr_inputline);
  }

  check_offset(1);
}

void readline_updowncase_word(int upcase)
{
  int stopsymbol_allowed = 1;

  while (*ptr_inputline) {
    if (!iswalnum(get_char(ptr_inputline))) {
      if (!stopsymbol_allowed) break;
    } else {
      stopsymbol_allowed = 0;
      if (upcase)
        put_char(ptr_inputline, towupper(get_char(ptr_inputline)));
      else
        put_char(ptr_inputline, towlower(get_char(ptr_inputline)));
    }
    ptr_inputline = next_char(ptr_inputline);
  }

  check_offset(1);
}

void readline_capitalize_word(void)
{
  int stopsymbol_allowed = 1;
  int upcased = 0;

  while (*ptr_inputline) {
    if (!iswalnum(get_char(ptr_inputline))) {
      if (!stopsymbol_allowed) break;
    } else {
      stopsymbol_allowed = 0;
      if (!upcased) {
        put_char(ptr_inputline, towupper(get_char(ptr_inputline)));
        upcased = 1;
      } else
        put_char(ptr_inputline, towlower(get_char(ptr_inputline)));
    }
    ptr_inputline = next_char(ptr_inputline);
  }

  check_offset(1);
}

void readline_backward_char(void)
{
  if (ptr_inputline == (char*)&inputLine) return;

  ptr_inputline = prev_char(ptr_inputline, inputLine);
  check_offset(-1);
}

void readline_forward_char(void)
{
  if (!*ptr_inputline) return;

  ptr_inputline = next_char(ptr_inputline);
  check_offset(1);
}

//  readline_accept_line(down_history)
// Validate current command line.
// If down_history is true, load the next history line.
void readline_accept_line(int down_history)
{
  scr_check_auto_away(TRUE);
  last_activity_buddy = current_buddy;
  process_line(inputLine);
  // Add line to history
  scr_cmdhisto_addline(inputLine);
  // Reset the line
  ptr_inputline = inputLine;
  *ptr_inputline = 0;
  inputline_offset = 0;

  if (down_history) {
    // Use next history line instead of a blank line
    const char *l = scr_cmdhisto_next("", 0);
    if (l) strcpy(inputLine, l);
    // Reset backup history line
    cmdhisto_backup[0] = 0;
  } else {
    // Reset history line pointer
    cmdhisto_cur = NULL;
  }
}

//  readline_clear_history()
// Clear command line history.
void readline_clear_history(void)
{
  scr_cmdhisto_reset();
}

void readline_cancel_completion(void)
{
  scr_cancel_current_completion();
  scr_end_current_completion();
  check_offset(-1);
}

void readline_do_completion(gboolean fwd)
{
  int i, n;

  if (multimode != 2) {
    // Not in verbatim multi-line mode
    scr_handle_tab(fwd);
  } else {
    // Verbatim multi-line mode: expand tab
    char tabstr[9];
    n = 8 - (ptr_inputline - inputLine) % 8;
    for (i = 0; i < n; i++)
      tabstr[i] = ' ';
    tabstr[i] = '\0';
    scr_insert_text(tabstr);
  }
  check_offset(0);
}

void readline_refresh_screen(void)
{
  scr_check_auto_away(TRUE);
  keypad(inputWnd, TRUE);
  parse_colors();
  scr_resize();
  redrawwin(stdscr);
}

void readline_disable_chat_mode(guint show_roster)
{
  scr_check_auto_away(TRUE);
  if (chatmode)
    scr_buffer_readmark(TRUE);
  currentWindow = NULL;
  chatmode = FALSE;
  if (current_buddy)
    buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
  if (show_roster)
    scr_roster_visibility(1);
  scr_update_chat_status(FALSE);
  top_panel(chatPanel);
  top_panel(inputPanel);
  update_panels();
}

void readline_hist_beginning_search_bwd(void)
{
  const char *l = scr_cmdhisto_prev(inputLine, ptr_inputline-inputLine);
  if (l) strcpy(inputLine, l);
}

void readline_hist_beginning_search_fwd(void)
{
  const char *l = scr_cmdhisto_next(inputLine, ptr_inputline-inputLine);
  if (l) strcpy(inputLine, l);
}

void readline_hist_prev(void)
{
  const char *l = scr_cmdhisto_prev(inputLine, 0);
  if (l) {
    strcpy(inputLine, l);
    // Set the pointer at the EOL.
    // We have to move it to BOL first, because we could be too far already.
    readline_iline_start();
    readline_iline_end();
  }
}

void readline_hist_next(void)
{
  const char *l = scr_cmdhisto_next(inputLine, 0);
  if (l) {
    strcpy(inputLine, l);
    // Set the pointer at the EOL.
    // We have to move it to BOL first, because we could be too far already.
    readline_iline_start();
    readline_iline_end();
  }
}

void readline_backward_kill_char(void)
{
  char *src, *c;

  if (ptr_inputline == (char*)&inputLine)
    return;

  src = ptr_inputline;
  c = prev_char(ptr_inputline, inputLine);
  ptr_inputline = c;
  _strmove(ptr_inputline, src);
  check_offset(-1);
}

void readline_forward_kill_char(void)
{
  if (!*ptr_inputline)
    return;

  _strmove(ptr_inputline, next_char(ptr_inputline));
}

void readline_iline_start(void)
{
  ptr_inputline = inputLine;
  inputline_offset = 0;
}

void readline_iline_end(void)
{
  for (; *ptr_inputline; ptr_inputline++) ;
  check_offset(1);
}

void readline_backward_kill_iline(void)
{
  char *dest = inputLine;

  if (ptr_inputline == inputLine) return;

  if (*dest == COMMAND_CHAR && ptr_inputline != dest+1)
    dest = next_char(dest);

  _strmove(dest, ptr_inputline);
  ptr_inputline = dest;
  inputline_offset = 0;
}

void readline_forward_kill_iline(void)
{
  *ptr_inputline = 0;
}

void readline_send_multiline(void)
{
  // Validate current multi-line
  if (multimode)
    process_command(mkcmdstr("msay send"), TRUE);
}

void readline_insert(const char *toinsert)
{
  if (!toinsert || !*toinsert) return;

  scr_insert_text(toinsert);
  check_offset(0);
}

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

  // Not a command?
  if ((ptr_inputline == inputLine) || (inputLine[0] != COMMAND_CHAR)) {
    if (!current_buddy) return -2;
    if (buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_ROOM) {
      *p_row = inputLine;
      return -1;
    }
    return -2;
  }

  // This is a command
  row = 0;
  for (p = inputLine ; p < ptr_inputline ; p = next_char(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.
static 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(LPRINT_LOGNORM, "Cannot insert text, line too long.");
    return;
  }

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

static void scr_cancel_current_completion(void);

//  scr_handle_tab()
// Function called when tab is pressed.
// Initiate or continue a completion...
// If fwd is false, a backward-completion is requested.
static void scr_handle_tab(gboolean fwd)
{
  int nrow;
  const char *row;
  const char *cchar;
  guint compl_categ;

  row = inputLine; // (Kills a GCC warning)
  nrow = which_row(&row);

  // a) No completion if no leading slash ('cause not a command),
  //    unless this is a room (then, it is a nickname completion)
  // b) We can't have more than 2 parameters (we use 2 flags)
  if ((nrow == -2) || (nrow == 3 && !completion_started) || nrow > 3)
    return;

  if (nrow == 0) {          // Command completion
    row = next_char(inputLine);
    compl_categ = COMPL_CMD;
  } else if (nrow == -1) {  // Nickname completion
    compl_categ = COMPL_RESOURCE;
  } else {                  // Other completion, depending on the command
    int alias = FALSE;
    cmd *com;
    char *xpline = expandalias(inputLine);
    com = cmd_get(xpline);
    if (xpline != inputLine) {
      // This is an alias, so we can't complete rows > 0
      alias = TRUE;
      g_free(xpline);
    }
    if ((!com && (!alias || !completion_started)) || !row) {
      scr_LogPrint(LPRINT_NORMAL, "I cannot complete that...");
      return;
    }
    if (!alias)
      compl_categ = com->completion_flags[nrow-1];
    else
      compl_categ = 0;
  }

  if (!completion_started) {
    guint dynlist;
    GSList *list;

    if (!compl_categ)
      return; // Nothing to complete

    list = compl_get_category_list(compl_categ, &dynlist);
    if (list) {
      guint n;
      char *prefix = g_strndup(row, ptr_inputline-row);
      // Init completion
      n = new_completion(prefix, list,
                         (compl_categ == COMPL_RESOURCE ?
                          settings_opt_get("muc_completion_suffix") : NULL));
      g_free(prefix);
      if (n == 0 && nrow == -1) {
        // This is a MUC room and we can't complete from the beginning of the
        // line.  Let's try a bit harder and complete the current word.
        row = prev_char(ptr_inputline, inputLine);
        while (row >= inputLine) {
          if (iswspace(get_char(row)) || get_char(row) == '(') {
              row = next_char((char*)row);
              break;
          }
          if (row == inputLine)
            break;
          row = prev_char((char*)row, inputLine);
        }
        // There's no need to try again if row == inputLine
        if (row > inputLine) {
          prefix = g_strndup(row, ptr_inputline-row);
          new_completion(prefix, list, NULL);
          g_free(prefix);
        }
      }
      // Free the list if it's a dynamic one
      if (dynlist) {
        GSList *slp;
        for (slp = list; slp; slp = g_slist_next(slp))
          g_free(slp->data);
        g_slist_free(list);
      }
      // Now complete
      cchar = complete(fwd);
      if (cchar)
        scr_insert_text(cchar);
      completion_started = TRUE;
    }
  } else {      // Completion already initialized
    scr_cancel_current_completion();
    // Now complete again
    cchar = complete(fwd);
    if (cchar)
      scr_insert_text(cchar);
  }
}

static void scr_cancel_current_completion(void)
{
  char *c;
  char *src = ptr_inputline;
  guint back = cancel_completion();
  guint i;
  // Remove $back chars
  for (i = 0; i < back; i++)
    ptr_inputline = prev_char(ptr_inputline, inputLine);
  c = ptr_inputline;
  for ( ; *src ; )
    *c++ = *src++;
  *c = 0;
}

static 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.
static inline void check_offset(int direction)
{
  int i;
  char *c = &inputLine[inputline_offset];
  // Left side
  if (inputline_offset && direction <= 0) {
    while (ptr_inputline <= c) {
      for (i = 0; i < 5; i++)
        c = prev_char(c, inputLine);
      if (c == inputLine)
        break;
    }
  }
  // Right side
  if (direction >= 0) {
    int delta = get_char_width(c);
    while (ptr_inputline > c) {
      c = next_char(c);
      delta += get_char_width(c);
    }
    c = &inputLine[inputline_offset];
    while (delta >= maxX) {
      for (i = 0; i < 5; i++) {
        delta -= get_char_width(c);
        c = next_char(c);
      }
    }
  }
  inputline_offset = c - inputLine;
}

#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
// prints inputLine with underlined words when misspelled
static inline void print_checked_line(void)
{
  char *wprint_char_fmt = "%c";
  int point;
  int nrchar = maxX;
  char *ptrCur = inputLine + inputline_offset;

#ifdef UNICODE
  // We need this to display a single UTF-8 char... Any better solution?
  if (utf8_mode)
    wprint_char_fmt = "%lc";
#endif

  wmove(inputWnd, 0, 0); // problem with backspace

  while (*ptrCur && nrchar-- > 0) {
    point = ptrCur - inputLine;
    if (maskLine[point])
      wattrset(inputWnd, A_UNDERLINE);
    wprintw(inputWnd, wprint_char_fmt, get_char(ptrCur));
    wattrset(inputWnd, A_NORMAL);
    ptrCur = next_char(ptrCur);
  }
}
#endif

static inline void refresh_inputline(void)
{
#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
  if (settings_opt_get_int("spell_enable")) {
    memset(maskLine, 0, INPUTLINE_LENGTH+1);
    spellcheck(inputLine, maskLine);
  }
  print_checked_line();
  wclrtoeol(inputWnd);
  if (*ptr_inputline) {
    // hack to set cursor pos. Characters can have different width,
    // so I know of no better way.
    char c = *ptr_inputline;
    *ptr_inputline = 0;
    print_checked_line();
    *ptr_inputline = c;
  }
#else
  mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset);
  wclrtoeol(inputWnd);
  if (*ptr_inputline) {
    // hack to set cursor pos. Characters can have different width,
    // so I know of no better way.
    char c = *ptr_inputline;
    *ptr_inputline = 0;
    mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset);
    *ptr_inputline = c;
  }
#endif
}

void scr_handle_CtrlC(void)
{
  if (!Curses) return;
  // Leave multi-line mode
  process_command(mkcmdstr("msay abort"), TRUE);
  // Same as Ctrl-g, now
  scr_cancel_current_completion();
  scr_end_current_completion();
  check_offset(-1);
  refresh_inputline();
}

static void add_keyseq(char *seqstr, guint mkeycode, gint value)
{
  keyseq *ks;

  // Let's make sure the length is correct
  if (strlen(seqstr) > MAX_KEYSEQ_LENGTH) {
    scr_LogPrint(LPRINT_LOGNORM, "add_keyseq(): key sequence is too long!");
    return;
  }

  ks = g_new0(keyseq, 1);
  ks->seqstr = g_strdup(seqstr);
  ks->mkeycode = mkeycode;
  ks->value = value;
  keyseqlist = g_slist_append(keyseqlist, ks);
}

//  match_keyseq(iseq, &ret)
// Check if "iseq" is a known key escape sequence.
// Return value:
// -1  if "seq" matches no known sequence
//  0  if "seq" could match 1 or more known sequences
// >0  if "seq" matches a key sequence; the mkey code is returned
//     and *ret is set to the matching keyseq structure.
static inline gint match_keyseq(int *iseq, keyseq **ret)
{
  GSList *ksl;
  keyseq *ksp;
  char *p, c;
  int *i;
  int needmore = FALSE;

  for (ksl = keyseqlist; ksl; ksl = g_slist_next(ksl)) {
    ksp = ksl->data;
    p = ksp->seqstr;
    i = iseq;
    while (1) {
      c = (unsigned char)*i;
      if (!*p && !c) { // Match
        (*ret) = ksp;
        return ksp->mkeycode;
      }
      if (!c) {
        // iseq is too short
        needmore = TRUE;
        break;
      } else if (!*p || c != *p) {
        // This isn't a match
        break;
      }
      p++; i++;
    }
  }

  if (needmore)
    return 0;
  return -1;
}

static inline int match_utf8_keyseq(int *iseq)
{
  int *strp = iseq;
  unsigned c = *strp++;
  unsigned mask = 0x80;
  int len = -1;
  while (c & mask) {
    mask >>= 1;
    len++;
  }
  if (len <= 0 || len > 4)
    return -1;
  c &= mask - 1;
  while ((*strp & 0xc0) == 0x80) {
    if (len-- <= 0) // can't happen
      return -1;
    c = (c << 6) | (*strp++ & 0x3f);
  }
  if (len)
    return 0;
  return c;
}

void scr_getch(keycode *kcode)
{
  keyseq *mks = NULL;
  int  ks[MAX_KEYSEQ_LENGTH+1];
  int i;

  memset(kcode, 0, sizeof(keycode));
  memset(ks,  0, sizeof(ks));

  kcode->value = wgetch(inputWnd);
  if (utf8_mode) {
    bool ismeta = (kcode->value == 27);
#ifdef NCURSES_MOUSE_VERSION
    bool ismouse = (kcode->value == KEY_MOUSE);

    if (ismouse) {
      MEVENT mouse;
      getmouse(&mouse);
      kcode->value = mouse.bstate;
      kcode->mcode = MKEY_MOUSE;
      return;
    } else if (ismeta)
#else
    if (ismeta)
#endif
      ks[0] = wgetch(inputWnd);
    else
      ks[0] = kcode->value;

    for (i = 0; i < MAX_KEYSEQ_LENGTH - 1; i++) {
      int match = match_utf8_keyseq(ks);
      if (match == -1)
        break;
      if (match > 0) {
        kcode->value = match;
        kcode->utf8 = 1;
        if (ismeta)
          kcode->mcode = MKEY_META;
        return;
      }
      ks[i + 1] = wgetch(inputWnd);
      if (ks[i + 1] == ERR)
        break;
    }
    while (i > 0)
      ungetch(ks[i--]);
    if (ismeta)
      ungetch(ks[0]);
    memset(ks,  0, sizeof(ks));
  }
  if (kcode->value != 27)
    return;

  // Check for escape key sequence
  for (i=0; i < MAX_KEYSEQ_LENGTH; i++) {
    int match;
    ks[i] = wgetch(inputWnd);
    if (ks[i] == ERR) break;
    match = match_keyseq(ks, &mks);
    if (match == -1) {
      // No such key sequence.  Let's increment i as it is a valid key.
      i++;
      break;
    }
    if (match > 0) {
      // We have a matching sequence
      kcode->mcode = mks->mkeycode;
      kcode->value = mks->value;
      return;
    }
  }

  // No match.  Let's return a meta-key.
  if (i > 0) {
    kcode->mcode = MKEY_META;
    kcode->value = ks[0];
  }
  if (i > 1) {
    // We need to push some keys back to the keyboard buffer
    while (i-- > 1)
      ungetch(ks[i]);
  }
  return;
}

void scr_do_update(void)
{
  if (colors_stalled)
    parse_colors();
  doupdate();
}

static void bindcommand(keycode kcode)
{
  gchar asciikey[16], asciicode[16];
  const gchar *boundcmd;

  if (kcode.utf8)
    g_snprintf(asciicode, 15, "U%d", kcode.value);
  else
    g_snprintf(asciicode, 15, "%d", kcode.value);

  if (!kcode.mcode || kcode.mcode == MKEY_EQUIV)
    g_snprintf(asciikey, 15, "%s", asciicode);
  else if (kcode.mcode == MKEY_META)
    g_snprintf(asciikey, 15, "M%s", asciicode);
  else if (kcode.mcode == MKEY_MOUSE)
    g_snprintf(asciikey, 15, "p%s", asciicode);
  else
    g_snprintf(asciikey, 15, "MK%d", kcode.mcode);

  boundcmd = settings_get(SETTINGS_TYPE_BINDING, asciikey);

  if (boundcmd) {
    gchar *cmdline = from_utf8(boundcmd);
    scr_check_auto_away(TRUE);
    process_command(cmdline, TRUE);
    g_free(cmdline);
    return;
  }

  scr_LogPrint(LPRINT_NORMAL, "Unknown key=%s", asciikey);
#ifndef UNICODE
  if (utf8_mode)
    scr_LogPrint(LPRINT_NORMAL,
                 "WARNING: Compiled without full UTF-8 support!");
#endif
}

//  scr_process_key(key)
// Handle the pressed key, in the command line (bottom).
void scr_process_key(keycode kcode)
{
  int key = kcode.value;
  int display_char = FALSE;

  lock_chatstate = FALSE;

  switch (kcode.mcode) {
    case 0:
        // key = kcode.value;
        break;
    case MKEY_EQUIV:
        // key = kcode.value;
        break;
    case MKEY_META:
    default:
        bindcommand(kcode);
        key = ERR; // Do not process any further
  }

  if (kcode.utf8) {
    if (key != ERR && !kcode.mcode)
      display_char = TRUE;
    goto display;
  }

  switch (key) {
    case 0:
    case ERR:
        break;
    case 9:     // Tab
        readline_do_completion(TRUE);   // Forward-completion
        break;
    case 353:   // Shift-Tab
        readline_do_completion(FALSE);  // Backward-completion
        break;
    case 13:    // Enter
    case 343:   // Enter on Maemo
        readline_accept_line(FALSE);
        break;
    case 3:     // Ctrl-C
        scr_handle_CtrlC();
        break;
    case KEY_RESIZE:
        scr_resize();
        break;
    default:
        display_char = TRUE;
  } // switch

display:
  if (display_char) {
    guint printable;

    if (kcode.utf8) {
      printable = iswprint(key);
    } else {
#ifdef __CYGWIN__
      printable = (isprint(key) || (key >= 161 && key <= 255))
                  && !is_speckey(key);
#else
      printable = isprint(key) && !is_speckey(key);
#endif
    }
    if (printable) {
      char tmpLine[INPUTLINE_LENGTH+1];

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

      // Insert char
      strcpy(tmpLine, ptr_inputline);
      ptr_inputline = put_char(ptr_inputline, key);
      strcpy(ptr_inputline, tmpLine);
      check_offset(1);
    } else {
      // Look for a key binding.
      if (!kcode.utf8)
        bindcommand(kcode);
    }
  }

  if (completion_started && key != 9 && key != 353 && key != KEY_RESIZE)
    scr_end_current_completion();
  refresh_inputline();

  if (!lock_chatstate) {
    // Set chat state to composing (1) if the user is currently composing,
    // i.e. not an empty line and not a command line.
    if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR)
      set_chatstate(0);
    else
      set_chatstate(1);
    if (chatstate)
      time(&chatstate_timestamp);
  }
  return;
}

#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
static void spell_checker_free(gpointer data)
{
  spell_checker* sc = data;
#ifdef WITH_ENCHANT
  enchant_broker_free_dict(sc->broker, sc->checker);
  enchant_broker_free(sc->broker);
#endif
#ifdef WITH_ASPELL
  delete_aspell_speller(sc->checker);
  delete_aspell_config(sc->config);
#endif
  g_free(sc);
}

static spell_checker* new_spell_checker(const char* spell_lang)
{
  spell_checker* sc = g_new(spell_checker, 1);
#ifdef WITH_ASPELL
  const char *spell_encoding = settings_opt_get("spell_encoding");
  AspellCanHaveError *possible_err;
  sc->config = new_aspell_config();
  if (spell_encoding)
    aspell_config_replace(sc->config, "encoding", spell_encoding);
  aspell_config_replace(sc->config, "lang", spell_lang);
  possible_err = new_aspell_speller(sc->config);

  if (aspell_error_number(possible_err) != 0) {
    delete_aspell_config(sc->config);
    g_free(sc);
    sc = NULL;
  } else {
    sc->checker = to_aspell_speller(possible_err);
  }
#endif
#ifdef WITH_ENCHANT
  sc->broker = enchant_broker_init();
  sc->checker = enchant_broker_request_dict(sc->broker, spell_lang);
  if (!sc->checker) {
    enchant_broker_free(sc->broker);
    g_free(sc);
    sc = NULL;
  }
#endif
  return sc;
}

// initialization
void spellcheck_init(void)
{
  int spell_enable            = settings_opt_get_int("spell_enable");
  const char *spell_lang     = settings_opt_get("spell_lang");
  gchar** langs;
  gchar** lang_iter;
  spell_checker* sc;

  if (!spell_enable)
    return;

  spellcheck_deinit();

  if (!spell_lang) { // Cannot initialize: language not specified
    scr_LogPrint(LPRINT_LOGNORM, "Error: Cannot initialize spell checker, language not specified.");
    scr_LogPrint(LPRINT_LOGNORM, "Please set the 'spell_lang' variable.");
    return;
  }

  langs = g_strsplit(spell_lang, " ", -1);
  for (lang_iter = langs; *lang_iter; ++lang_iter) {
    if (**lang_iter) { // Skip empty strings
      sc = new_spell_checker(*lang_iter);
      if (sc) {
        spell_checkers = g_slist_append(spell_checkers, sc);
      } else {
        scr_LogPrint(LPRINT_LOGNORM,
                     "Warning: Could not load spell checker language '%s'.",
                     *lang_iter);
      }
    }
  }
  g_strfreev(langs);
}

// Deinitialization of spellchecker
void spellcheck_deinit(void)
{
  g_slist_free_full(spell_checkers, spell_checker_free);
  spell_checkers = NULL;
}

typedef struct {
  const char* str;
  int len;
} spell_substring;

static int spellcheckword(gconstpointer sc_ptr, gconstpointer substr_ptr)
{
  spell_checker* sc = (spell_checker*) sc_ptr;
  spell_substring* substr = (spell_substring*) substr_ptr;
#ifdef WITH_ENCHANT
  // enchant_dict_check will return 0 on good word
  return enchant_dict_check(sc->checker, substr->str, substr->len);
#endif
#ifdef WITH_ASPELL
  // aspell_speller_check will return 1 on good word, so we need to make it 0
  return aspell_speller_check(sc->checker, substr->str, substr->len) - 1;
#endif
  return 0; // Keep compiler happy
}

#define spell_isalpha(c) (utf8_mode ? iswalpha(get_char(c)) : isalpha(*c))

// Spell checking function
static void spellcheck(char *line, char *checked)
{
  const char *start, *line_start;
  spell_substring substr;

  if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR)
    return;

  // Give up early if not languages are loaded
  if (!spell_checkers)
    return;

  line_start = line;

  while (*line) {

    if (!spell_isalpha(line)) {
      line = next_char(line);
      continue;
    }

    if (!strncmp(line, "http://", 7)) {
      line += 7; // : and / characters are 1 byte long in utf8, right?

      while (!strchr(" \t\r\n", *line))
        line = next_char(line); // i think line++ would be fine here?

      continue;
    }

    if (!strncmp(line, "ftp://", 6)) {
      line += 6;

      while (!strchr(" \t\r\n", *line))
        line = next_char(line);

      continue;
    }

    start = line;

    while (spell_isalpha(line))
      line = next_char(line);

    substr.str = start;
    substr.len = line - start;
    if (!g_slist_find_custom(spell_checkers, &substr, spellcheckword))
      memset(&checked[start - line_start], SPELLBADCHAR, line - start);
  }
}
#endif

/* vim: set et cindent cinoptions=>2\:2(0 ts=2 sw=2:  For Vim users... */
