/*
* 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 void open_chat_window(void);
static void clear_inputline(void);
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_vi_mode_guard(const gchar *key, const gchar *new_value)
{
int new_mode = 0;
if (new_value)
new_mode = atoi(new_value);
if (new_mode == 0 || new_mode == 1)
vi_mode = new_mode;
return g_strdup(new_value);
}
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
}
// Set up vi_mode guard
settings_set_guard("vi_mode", scr_vi_mode_guard);
if (settings_opt_get_int("vi_mode") == 1)
vi_mode = true;
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(×tamp));
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(×tamp));
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(×tamp));
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;
buddylist_build();
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;
}
roster_msg_update_unread(bjid, FALSE);
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_update_unread(winId, FALSE);
roster_msg_setflag(winId, FALSE, FALSE);
scr_update_roster();
} else if (setmsgflg) {
roster_msg_update_unread(winId, TRUE);
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, *unread;
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);
unread = g_new0(char, Roster_Width+1);;
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);
}
}
name[0] = 0;
unread[0] = 0;
if (Roster_Width > prefix_length) {
g_utf8_strncpy(name, buddy_getname(BUDDATA(buddy)), Roster_Width-prefix_length);
if (settings_opt_get_int("roster_show_unread_count")) {
guint unread_count = buddy_getunread(BUDDATA(buddy));
glong name_length = g_utf8_strlen(name, 4*Roster_Width);
if (unread_count > 0 && Roster_Width > prefix_length + name_length) {
snprintf(unread, Roster_Width-(prefix_length+name_length)+1, " (%u)", unread_count);
}
}
}
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%s",
space, pending, sepleft, status, sepright, name, unread);
}
rline_locale = from_utf8(rline);
mvwprintw(rosterWnd, i, x_pos, "%s", rline_locale);
g_free(rline_locale);
i++;
}
g_free(rline);
g_free(unread);
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();
}
}
buddylist_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);
if (vi_mode)
clear_inputline();
}
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") && (chatmode || !vi_mode)) {
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
}
static void scr_process_vi_arrow_key(int key)
{
const char *l;
char mask[INPUTLINE_LENGTH+1];
size_t cmd_len = strlen(mask);
size_t str_len = strlen(inputLine) - 1;
strncpy(mask, mkcmdstr("roster search "), INPUTLINE_LENGTH);
if (inputLine[0] == COMMAND_CHAR) {
if (!chatmode) { // Command mode
if (key == KEY_UP)
l = scr_cmdhisto_prev(inputLine, ptr_inputline - inputLine);
else
l = scr_cmdhisto_next(inputLine, ptr_inputline - inputLine);
if (l)
strcpy(inputLine, l);
return;
}
// Chat mode
if (cmd_len + str_len > INPUTLINE_LENGTH)
return;
memcpy(mask + cmd_len, inputLine + 1, str_len + 1);
if (key == KEY_UP)
l = scr_cmdhisto_prev(mask, ptr_inputline - inputLine + cmd_len - 1);
else
l = scr_cmdhisto_next(mask, ptr_inputline - inputLine + cmd_len - 1);
if (l)
strcpy(inputLine + 1, l + cmd_len);
return;
}
if (inputLine[0] == VI_SEARCH_COMMAND_CHAR)
return;
if (key == KEY_UP)
process_command(mkcmdstr("roster up"), TRUE);
else if (key == KEY_DOWN)
process_command(mkcmdstr("roster down"), TRUE);
}
// 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;
int vi_search = FALSE;
static int ex_or_search_mode = 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 (vi_mode && !chatmode) {
int got_cmd_prefix = FALSE;
int unrecognized = FALSE;
static char search_cmd[INPUTLINE_LENGTH+1] = "";
if (key == KEY_UP || key == KEY_DOWN) {
scr_process_vi_arrow_key(key);
key = ERR; // Do not process any further
} else if (ex_or_search_mode) {
switch (key) {
case 27: // Escape
clear_inputline();
ex_or_search_mode = FALSE;
break;
case 9: // Tab
case 353: // Shift-Tab
if (inputLine[0] == VI_SEARCH_COMMAND_CHAR)
vi_search = TRUE;
break;
case 13: // Enter
case 343: // Enter on Maemo
if (inputLine[0] == COMMAND_CHAR) {
{
char *p = strchr(inputLine, 0);
while (*--p == ' ' && p > inputLine)
*p = 0;
}
if (!strcmp(inputLine, mkcmdstr("x")) ||
!strcmp(inputLine, mkcmdstr("q")) ||
!strcmp(inputLine, mkcmdstr("wq")))
strcpy(inputLine, mkcmdstr("quit"));
if (isdigit((int)(unsigned char)inputLine[1]) &&
strlen(inputLine) <= 9) {
process_command(mkcmdstr("roster top"), TRUE);
memcpy(inputLine + 13, inputLine + 1, 10);
memcpy(inputLine + 1, "roster down ", 12);
}
inputLine[0] = COMMAND_CHAR;
process_command(inputLine, TRUE);
scr_cmdhisto_addline(inputLine);
} else if (inputLine[0] == VI_SEARCH_COMMAND_CHAR) {
size_t cmd_len;
size_t str_len = strlen(inputLine) - 1;
strncpy(search_cmd, mkcmdstr("roster search "), INPUTLINE_LENGTH);
cmd_len = strlen(search_cmd);
if (cmd_len + str_len > INPUTLINE_LENGTH)
return;
memcpy(search_cmd + cmd_len, inputLine + 1, str_len + 1);
process_command(search_cmd, TRUE);
scr_cmdhisto_addline(search_cmd);
} else if (inputLine[0] == 0) {
if (buddy_gettype(BUDDATA(current_buddy)) ==
ROSTER_TYPE_GROUP)
process_command(mkcmdstr("group toggle"), TRUE);
else
open_chat_window();
}
ex_or_search_mode = FALSE;
}
} else if (key >= '0' && key <= '9') {
got_cmd_prefix = TRUE;
} else if (key == COMMAND_CHAR || key == VI_SEARCH_COMMAND_CHAR) {
ex_or_search_mode = TRUE;
cmdhisto_cur = NULL;
} else {
switch (key) {
case ' ':
process_command(mkcmdstr("group toggle"), TRUE);
break;
case '!':
{
const char *bjid = buddy_getjid(BUDDATA(current_buddy));
if (bjid) {
guint type = buddy_gettype(BUDDATA(current_buddy));
guint prio = buddy_getuiprio(BUDDATA(current_buddy));
if (type & ROSTER_TYPE_ROOM &&
prio < ROSTER_UI_PRIO_MUC_HL_MESSAGE) {
roster_setuiprio(bjid, FALSE,
ROSTER_UI_PRIO_MUC_HL_MESSAGE, prio_set);
roster_msg_setflag(bjid, FALSE, TRUE);
} else if (type & ROSTER_TYPE_USER &&
prio < ROSTER_UI_PRIO_ATTENTION_MESSAGE) {
roster_setuiprio(bjid, FALSE,
ROSTER_UI_PRIO_ATTENTION_MESSAGE, prio_set);
roster_msg_setflag(bjid, FALSE, TRUE);
} else {
roster_msg_setflag(bjid, FALSE, FALSE);
}
scr_update_roster();
}
}
break;
case '#':
{
const char *bjid = buddy_getjid(BUDDATA(current_buddy));
if (bjid) {
unsigned short bflags = buddy_getflags(BUDDATA(current_buddy));
if (bflags & ROSTER_FLAG_MSG)
roster_msg_setflag(bjid, FALSE, FALSE);
else
roster_msg_setflag(bjid, FALSE, TRUE);
scr_update_roster();
}
}
break;
case '\'':
if (inputLine[0] == '\'')
process_command(mkcmdstr("roster alternate"), TRUE);
else
got_cmd_prefix = TRUE;
break;
case 'A':
process_command(mkcmdstr("roster unread_first"), TRUE);
break;
case 'a':
process_command(mkcmdstr("roster unread_next"), TRUE);
break;
case 'F':
process_command(mkcmdstr("roster group_prev"), TRUE);
break;
case 'f':
process_command(mkcmdstr("roster group_next"), TRUE);
break;
case 'G':
process_command(mkcmdstr("roster bottom"), TRUE);
break;
case 'g':
if (inputLine[0] == 'g')
process_command(mkcmdstr("roster top"), TRUE);
else {
clear_inputline();
got_cmd_prefix = TRUE;
}
break;
case 'i':
open_chat_window();
break;
case 'j':
if (isdigit((int)(unsigned char)inputLine[0]) &&
strlen(inputLine) <= 9) {
char down_cmd[32];
strncpy(down_cmd, mkcmdstr("roster down "), 32);
strncat(down_cmd, inputLine, 16);
process_command(down_cmd, TRUE);
} else
process_command(mkcmdstr("roster down"), TRUE);
break;
case 'k':
if (isdigit((int)(unsigned char)inputLine[0]) &&
strlen(inputLine) <= 9) {
char up_cmd[32];
strncpy(up_cmd, mkcmdstr("roster up "), 32);
strncat(up_cmd, inputLine, 16);
process_command(up_cmd, TRUE);
} else
process_command(mkcmdstr("roster up "), TRUE);
break;
case 'M':
if (inputLine[0] == 'z') {
GSList *groups = compl_list(ROSTER_TYPE_GROUP);
GSList *g;
for (g = groups; g; g = g_slist_next(g)) {
char fold_cmd[256];
size_t cmd_len, grp_len;
strncpy(fold_cmd, mkcmdstr("group fold "), 32);
cmd_len = strlen(fold_cmd);
grp_len = strlen(g->data);
if (cmd_len + grp_len + 1 > sizeof(fold_cmd))
continue;
memcpy(fold_cmd + cmd_len, g->data, grp_len + 1);
process_command(fold_cmd, TRUE);
g_free(g->data);
}
g_slist_free(groups);
} else
unrecognized = TRUE;
break;
case 'n':
process_command(search_cmd, TRUE);
break;
case 'O':
process_command(mkcmdstr("roster unread_first"), TRUE);
open_chat_window();
break;
case 'o':
process_command(mkcmdstr("roster unread_next"), TRUE);
open_chat_window();
break;
case 'R':
if (inputLine[0] == 'z') {
GSList *groups = compl_list(ROSTER_TYPE_GROUP);
GSList *g;
for (g = groups; g; g = g_slist_next(g)) {
char fold_cmd[256];
size_t cmd_len, grp_len;
strncpy(fold_cmd, mkcmdstr("group unfold "), 32);
cmd_len = strlen(fold_cmd);
grp_len = strlen(g->data);
if (cmd_len + grp_len + 1 > sizeof(fold_cmd))
continue;
memcpy(fold_cmd + cmd_len, g->data, grp_len + 1);
process_command(fold_cmd, TRUE);
g_free(g->data);
}
g_slist_free(groups);
} else
unrecognized = TRUE;
break;
case 'Z':
if (inputLine[0] == 'Z')
process_command(mkcmdstr("quit"), TRUE);
else {
clear_inputline();
got_cmd_prefix = TRUE;
}
break;
case 'z':
clear_inputline();
got_cmd_prefix = TRUE;
break;
case 13: // Enter
case 343: // Enter on Maemo
if (inputLine[0] == 0) {
if (buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_GROUP)
process_command(mkcmdstr("group toggle"), TRUE);
else
open_chat_window();
}
break;
default:
unrecognized = TRUE;
break;
}
cmdhisto_cur = NULL;
}
if (!ex_or_search_mode && !got_cmd_prefix) {
clear_inputline();
if (!unrecognized)
key = ERR; // Do not process any further
}
lock_chatstate = TRUE;
}
if (kcode.utf8) {
if (key != ERR && !kcode.mcode)
display_char = TRUE;
goto display;
}
switch (key) {
case 0:
case ERR:
break;
case 9: // Tab
if (!vi_search)
readline_do_completion(TRUE); // Forward-completion
break;
case 353: // Shift-Tab
if (!vi_search)
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 (ex_or_search_mode &&
inputLine[0] != COMMAND_CHAR && inputLine[0] != VI_SEARCH_COMMAND_CHAR)
ex_or_search_mode = FALSE;
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_foreach (spell_checkers, (GFunc) spell_checker_free, NULL);
g_slist_free (spell_checkers);
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
static void open_chat_window(void)
{
last_activity_buddy = current_buddy;
scr_check_auto_away(TRUE);
scr_set_chatmode(TRUE);
scr_show_buddy_window();
}
static void clear_inputline(void)
{
ptr_inputline = inputLine;
*ptr_inputline = 0;
inputline_offset = 0;
}
/* vim: set et cindent cinoptions=>2\:2(0 ts=2 sw=2: For Vim users... */