Mercurial > ~mikael > mcabber > hg
diff mcabber/mcabber/screen.c @ 1668:41c26b7d2890
Install mcabber headers
* Change mcabber headers naming scheme
* Move 'src/' -> 'mcabber/'
* Add missing include <mcabber/config.h>'s
* Create and install clean config.h version in 'include/'
* Move "dirty" config.h version to 'mcabber/'
* Add $(top_srcdir) to compiler include path
* Update modules HOWTO
author | Myhailo Danylenko <isbear@ukrpost.net> |
---|---|
date | Mon, 18 Jan 2010 15:36:19 +0200 |
parents | mcabber/src/screen.c@1a4890514eb9 |
children | 9a0ed33fb91b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/screen.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,4124 @@ +/* + * screen.c -- UI stuff + * + * Copyright (C) 2005-2009 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#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> +#endif + +#ifdef WITH_ASPELL +# include <aspell.h> +#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) + +const char *LocaleCharSet = "C"; + +static unsigned short int Log_Win_Height; +static unsigned short int Roster_Width; + +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(void); + +#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) +static void spellcheck(char *, char *); +#endif + +static GHashTable *winbufhash; + +typedef struct { + GList *hbuf; + GList *top; // If top is NULL, we'll display the last lines + char cleared; // For ex, user has issued a /clear command... + char lock; +} 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; +int update_roster; +int utf8_mode = 0; +static bool Curses; +static bool log_win_on_top; +static bool roster_win_on_right; +static time_t LastActivity; + +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 chatstates_disabled; + +#define MAX_KEYSEQ_LENGTH 8 + +typedef struct { + char *seqstr; + guint mkeycode; + gint value; +} keyseq; + +#ifdef HAVE_GLIB_REGEX +static GRegex *url_regex; +#endif + +GSList *keyseqlist; +static void add_keyseq(char *seqstr, guint mkeycode, gint value); + +void scr_WriteInWindow(const char *winId, const char *text, time_t timestamp, + unsigned int prefix_flags, int force_show, + unsigned mucnicklen, gpointer xep184); + +void scr_WriteMessage(const char *bjid, const char *text, + time_t timestamp, guint prefix_flags, + unsigned mucnicklen, gpointer xep184); + +inline void scr_UpdateBuddyWindow(void); +inline void scr_set_chatmode(int enable); + +#define SPELLBADCHAR 5 + +#ifdef WITH_ENCHANT +EnchantBroker *spell_broker; +EnchantDict *spell_checker; +#endif + +#ifdef WITH_ASPELL +AspellConfig *spell_config; +AspellSpeller *spell_checker; +#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 FindColor(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) + 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 = FindColor(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_MucColor(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_UpdateBuddyWindow(); +} + +// 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_MucNickColor(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_UpdateBuddyWindow(); +} + +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_RosterClearColor(void) +{ + GSList *head; + for (head = rostercolrules; head; head = g_slist_next(head)) { + free_rostercolrule(head->data); + } + g_slist_free(rostercolrules); + rostercolrules = NULL; +} + +// 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_RosterColor(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); + 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); + } + return TRUE; + } +} + +static void ParseColors(void) +{ + const char *colors[] = { + "", "", + "general", + "msgout", + "msghl", + "status", + "roster", + "rostersel", + "rosterselmsg", + "rosternewmsg", + "info", + "msgin", + 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) ? FindColor(color) : COLOR_WHITE), + FindColor(background)); + break; + case COLOR_MSGOUT: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_CYAN), + FindColor(background)); + break; + case COLOR_MSGHL: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_YELLOW), + FindColor(background)); + break; + case COLOR_STATUS: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE), + FindColor(backstatus)); + break; + case COLOR_ROSTER: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_GREEN), + FindColor(background)); + break; + case COLOR_ROSTERSEL: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_BLUE), + FindColor(backselected)); + break; + case COLOR_ROSTERSELNMSG: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_RED), + FindColor(backselected)); + break; + case COLOR_ROSTERNMSG: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_RED), + FindColor(background)); + break; + case COLOR_INFO: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE), + FindColor(background)); + break; + case COLOR_MSGIN: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE), + FindColor(background)); + break; + } + } + for (i = COLOR_max; i < (COLOR_max + COLORS); i++) + init_pair(i, i-COLOR_max, FindColor(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; + } + } +} + +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_InitLocaleCharSet(void) +{ + setlocale(LC_ALL, ""); +#ifdef HAVE_LOCALCHARSET_H + LocaleCharSet = locale_charset(); +#else + LocaleCharSet = nl_langinfo(CODESET); +#endif + utf8_mode = (strcmp(LocaleCharSet, "UTF-8") == 0); +} + +void scr_InitCurses(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 + } + + ParseColors(); + + getmaxyx(stdscr, maxY, maxX); + Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT; + // Note scr_DrawMainWindow() should be called early after scr_InitCurses() + // to update Log_Win_Height and set max{X,Y} + + inputLine[0] = 0; + ptr_inputline = inputLine; + + if (settings_opt_get("url_regex")) { +#ifdef HAVE_GLIB_REGEX + url_regex = g_regex_new(settings_opt_get("url_regex"), + G_REGEX_OPTIMIZE, 0, NULL); +#else + scr_LogPrint(LPRINT_LOGNORM, "ERROR: Your glib version is too old, " + "cannot use url_regex."); +#endif // HAVE_GLIB_REGEX + } + + Curses = TRUE; + return; +} + +void scr_TerminateCurses(void) +{ + if (!Curses) return; + clear(); + refresh(); + endwin(); +#ifdef HAVE_GLIB_REGEX + if (url_regex) + g_regex_unref(url_regex); +#endif + 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)]; +} + +// 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_LogPrint(...) +// 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_LogPrint(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_WriteInWindow(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_WriteLog(flag, buffer); + g_free(buffer); + } + g_free(btext); +} + +static winbuf *scr_SearchWindow(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_BuddyBufferExists(const char *bjid) +{ + return (scr_SearchWindow(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; + + 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) { + char *id; + id = hlog_get_log_jid(title); + if (id) { + winbuf *wb = scr_SearchWindow(id, FALSE); + if (!wb) + wb = scr_new_buddy(id, TRUE); + tmp->bd=wb->bd; + 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()); + } + + id = g_strdup(title); + mc_strtolower(id); + g_hash_table_insert(winbufhash, id, tmp); + } else { + tmp->bd = g_new0(buffdata, 1); + } + 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). +void scr_line_prefix(hbb_line *line, char *pref, guint preflen) +{ + char date[64]; + + if (line->timestamp && + !(line->flags & (HBB_PREFIX_SPECIAL|HBB_PREFIX_CONT))) { + 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) { + 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, " "); + } +} + +// scr_UpdateWindow() +// (Re-)Display the given chat window. +static void scr_UpdateWindow(winbuf *win_entry) +{ + int n; + guint prefixwidth; + char pref[96]; + hbb_line **lines, *line; + GList *hbuf_head; + int color; + + prefixwidth = scr_getprefixwidth(); + prefixwidth = MIN(prefixwidth, sizeof pref); + + // Should the window be empty? + if (win_entry->bd->cleared) { + werase(win_entry->win); + 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. + lines = hbuf_get_lines(hbuf_head, CHAT_WIN_HEIGHT); + + // Display these lines + for (n = 0; n < CHAT_WIN_HEIGHT; n++) { + wmove(win_entry->win, n, 0); + line = *(lines+n); + if (line) { + 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 + scr_line_prefix(line, pref, prefixwidth); + wprintw(win_entry->win, pref); + + // Make sure we are at the right position + wmove(win_entry->win, n, 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); + + // Return the color back + 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; + } + } + g_free(lines); +} + +static winbuf *scr_CreateWindow(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_ShowWindow() +// Display the chat window with the given identifier. +// "special" must be true if this is a special buffer window. +static void scr_ShowWindow(const char *winId, int special) +{ + winbuf *win_entry; + + win_entry = scr_SearchWindow(winId, special); + + if (!win_entry) { + win_entry = scr_CreateWindow(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); + update_roster = TRUE; + + // Refresh the window + scr_UpdateWindow(win_entry); + + // Finished :) + update_panels(); + + top_panel(inputPanel); +} + +// scr_ShowBuddyWindow() +// Display the chat window buffer for the current buddy. +void scr_ShowBuddyWindow(void) +{ + const gchar *bjid; + + if (!current_buddy) { + bjid = NULL; + } else { + bjid = CURRENT_JID; + if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL) { + scr_ShowWindow(buddy_getname(BUDDATA(current_buddy)), TRUE); + return; + } + } + + if (!bjid) { + top_panel(chatPanel); + top_panel(inputPanel); + currentWindow = NULL; + return; + } + + scr_ShowWindow(bjid, FALSE); +} + +// scr_UpdateBuddyWindow() +// (Re)Display the current window. +// If chatmode is enabled, call scr_ShowBuddyWindow(), +// else display the chat window. +inline void scr_UpdateBuddyWindow(void) +{ + if (chatmode) { + scr_ShowBuddyWindow(); + return; + } + + top_panel(chatPanel); + top_panel(inputPanel); +} + +// scr_WriteInWindow() +// 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. +void scr_WriteInWindow(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; + char *nicktmp, *nicklocaltmp; + + // Look for the window entry. + special = (winId == NULL); + win_entry = scr_SearchWindow(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_CreateWindow(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); + 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; + // Show and refresh the window + top_panel(win_entry->panel); + scr_UpdateWindow(win_entry); + top_panel(inputPanel); + update_panels(); + } else if (!(prefix_flags & HBB_PREFIX_NOFLAG)) { + setmsgflg = TRUE; + } + if (setmsgflg && !special) { + if (special && !winId) + winId = SPECIAL_BUFFER_STATUS_ID; + roster_msg_setflag(winId, special, TRUE); + update_roster = TRUE; + } +} + +// scr_UpdateMainStatus() +// Redraw the main (bottom) status line. +void scr_UpdateMainStatus(int forceupdate) +{ + char *sm = from_utf8(xmpp_getstatusmsg()); + const char *info = settings_opt_get("info"); + + werase(mainstatusWnd); + if (info) { + char *info_locale = from_utf8(info); + mvwprintw(mainstatusWnd, 0, 0, "%c[%c] %s: %s", + (unread_msg(NULL) ? '#' : ' '), + imstatus2char[xmpp_getstatus()], + info_locale, (sm ? sm : "")); + g_free(info_locale); + } else + mvwprintw(mainstatusWnd, 0, 0, "%c[%c] %s", + (unread_msg(NULL) ? '#' : ' '), + imstatus2char[xmpp_getstatus()], (sm ? sm : "")); + if (forceupdate) { + top_panel(inputPanel); + update_panels(); + } + g_free(sm); +} + +// scr_DrawMainWindow() +// 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_DrawMainWindow(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; + + 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, NULL); + /* 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_TerminateCurses(); + 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)); + } 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_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 + update_roster = TRUE; + 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_DrawMainWindow() 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_DrawMainWindow(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_ShowBuddyWindow(); +} + +// scr_UpdateChatStatus(forceupdate) +// Redraw the buddy status bar. +// Set forceupdate to TRUE if update_panels() must be called. +void scr_UpdateChatStatus(int forceupdate) +{ + unsigned short btype, isgrp, ismuc, isspe; + const char *btypetext = "Unknown"; + const char *fullname; + 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_UpdateMainStatus(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_SearchWindow(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 = '?'; + + 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), NULL); + if (budstate < imstatus_size) + status = imstatus2char[budstate]; + } + + // No status message for MUC rooms + if (!ismuc) { + 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(buf_locale); + g_free(buf); + + // Display chatstates of the contact, if available. + if (btype & ROSTER_TYPE_USER) { + char eventchar = 0; + guint event; + + // We do not specify the resource here, so one of the resources with the + // highest priority will be used. + event = buddy_resource_getevents(BUDDATA(current_buddy), NULL); + + 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_DrawRoster() +// Display the buddylist (not really the roster) on the screen +void scr_DrawRoster(void) +{ + static int offset = 0; + char *name, *rline; + int maxx, maxy; + GList *buddy; + int i, n; + int rOffset; + int cursor_backup; + char status, pending; + enum imstatus currentstatus = xmpp_getstatus(); + int x_pos; + + // We can reset update_roster + update_roster = FALSE; + + getmaxyx(rosterWnd, maxy, maxx); + maxx--; // Last char is for vertical border + + cursor_backup = curs_set(0); + + if (!buddylist) + offset = 0; + else + scr_UpdateChatStatus(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; + + name = g_new0(char, 4*Roster_Width); + rline = g_new0(char, 4*Roster_Width+1); + + buddy = buddylist; + rOffset = offset; + + for (i=0; i<maxy && buddy; buddy = g_list_next(buddy)) { + unsigned short bflags, btype, ismsg, isgrp, ismuc, ishid, isspe; + gchar *rline_locale; + GSList *resources, *p_res; + + 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; + + if (rOffset > 0) { + rOffset--; + continue; + } + + status = '?'; + pending = ' '; + + resources = buddy_getresources(BUDDATA(buddy)); + 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 *jid = buddy_getjid(BUDDATA(buddy)); + for (head = rostercolrules; head; head = g_slist_next(head)) { + rostercolor *rc = head->data; + if (g_pattern_match_string(rc->compiled, jid) && + (!strcmp("*", rc->status) || strchr(rc->status, status))) { + color = compose_color(rc->color); + break; + } + } + } + wattrset(rosterWnd, color); + } + } + + if (Roster_Width > 7) + g_utf8_strncpy(name, buddy_getname(BUDDATA(buddy)), Roster_Width-7); + else + name[0] = 0; + + 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, " %c+++ %s (%i)", 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, " %c--- %s", pending, name); + } + else + snprintf(rline, 4*Roster_Width, " %c--- %s", pending, name); + } else if (isspe) { + snprintf(rline, 4*Roster_Width, " %c%s", 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, + " %c%c%c%c %s", pending, sepleft, status, sepright, name); + } + + rline_locale = from_utf8(rline); + mvwprintw(rosterWnd, i, x_pos, "%s", rline_locale); + g_free(rline_locale); + i++; + } + + g_free(rline); + g_free(name); + top_panel(inputPanel); + update_panels(); + curs_set(cursor_backup); +} + +// scr_RosterVisibility(status) +// Set the roster visibility: +// status=1 Show roster +// status=0 Hide roster +// status=-1 Toggle roster status +void scr_RosterVisibility(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); + } +} + +#ifdef HAVE_GLIB_REGEX +static inline void scr_LogUrls(const gchar *string) +{ + GMatchInfo *match_info; + + g_regex_match_full(url_regex, string, -1, 0, 0, &match_info, NULL); + while (g_match_info_matches(match_info)) { + gchar *url = g_match_info_fetch(match_info, 0); + scr_print_logwindow(url); + g_free(url); + g_match_info_next(match_info, NULL); + } + g_match_info_free(match_info); +} +#endif + +void scr_WriteMessage(const char *bjid, const char *text, + time_t timestamp, guint prefix_flags, + unsigned mucnicklen, gpointer xep184) +{ + char *xtext; + + if (!timestamp) timestamp = time(NULL); + + xtext = ut_expand_tabs(text); // Expand tabs and filter out some chars + + scr_WriteInWindow(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_WriteIncomingMessage(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)) + prefix |= HBB_PREFIX_IN; + +#ifdef HAVE_GLIB_REGEX + if (url_regex) + scr_LogUrls(text); +#endif + scr_WriteMessage(jidfrom, text, timestamp, prefix, mucnicklen, NULL); +} + +void scr_WriteOutgoingMessage(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_WriteMessage(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_ShowWindow(jidto, FALSE); +} + +void scr_RemoveReceiptFlag(const char *bjid, gpointer xep184) +{ + winbuf *win_entry = scr_SearchWindow(bjid, FALSE); + if (win_entry) { + hbuf_remove_receipt(win_entry->bd->hbuf, xep184); + if (chatmode && (buddy_search_jid(bjid) == current_buddy)) + scr_UpdateBuddyWindow(); + } +} + +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 inline void set_chatstate(int state) +{ +#if defined JEP0022 || defined JEP0085 + 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_ChatStatesTimeout, + 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 +} + +#if defined JEP0022 || defined JEP0085 +gboolean scr_ChatStatesTimeout(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 + +// Check if we should enter/leave automatic away status +void scr_CheckAutoAway(int activity) +{ + enum imstatus cur_st; + unsigned int autoaway_timeout = settings_opt_get_int("autoaway"); + + if (Autoaway && activity) set_autoaway(FALSE); + if (!autoaway_timeout) return; + if (!LastActivity || activity) time(&LastActivity); + + cur_st = xmpp_getstatus(); + // Auto-away is disabled for the following states + if ((cur_st != available) && (cur_st != freeforchat)) + return; + + if (!activity) { + time_t now; + time(&now); + if (!Autoaway && (now > LastActivity + (time_t)autoaway_timeout)) + set_autoaway(TRUE); + } +} + +// 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) + 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); + // We should rebuild the buddylist but not everytime + // Here we check if we were locking a buddy who is actually offline, + // and hide_offline_buddies is TRUE. In which case we need to rebuild. + if (!(buddylist_get_filter() & 1<<prev_st)) + buddylist_build(); + update_roster = TRUE; +} + +// scr_RosterTop() +// Go to the first buddy in the buddylist +void scr_RosterTop(void) +{ + set_current_buddy(buddylist); + if (chatmode) + scr_ShowBuddyWindow(); +} + +// scr_RosterBottom() +// Go to the last buddy in the buddylist +void scr_RosterBottom(void) +{ + set_current_buddy(g_list_last(buddylist)); + if (chatmode) + scr_ShowBuddyWindow(); +} + +// scr_RosterUpDown(updown, n) +// Go to the nth next buddy in the buddylist +// (up if updown == -1, down if updown == 1) +void scr_RosterUpDown(int updown, unsigned int n) +{ + unsigned int i; + + if (updown < 0) { + for (i = 0; i < n; i++) + set_current_buddy(g_list_previous(current_buddy)); + } else { + for (i = 0; i < n; i++) + set_current_buddy(g_list_next(current_buddy)); + } + if (chatmode) + scr_ShowBuddyWindow(); +} + +// scr_RosterPrevGroup() +// Go to the previous group in the buddylist +void scr_RosterPrevGroup(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) + scr_ShowBuddyWindow(); + break; + } + } +} + +// scr_RosterNextGroup() +// Go to the next group in the buddylist +void scr_RosterNextGroup(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) + scr_ShowBuddyWindow(); + break; + } + } +} + +// scr_RosterSearch(str) +// Look forward for a buddy with jid/name containing str. +void scr_RosterSearch(char *str) +{ + set_current_buddy(buddy_search(str)); + if (chatmode) + scr_ShowBuddyWindow(); +} + +// scr_RosterJumpJid(bjid) +// Jump to buddy bjid. +// NOTE: With this function, the buddy is added to the roster if doesn't exist. +void scr_RosterJumpJid(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_build(); + // Jump to the buddy + set_current_buddy(buddy_search_jid(barejid)); + if (chatmode) + scr_ShowBuddyWindow(); +} + +// scr_RosterUnreadMessage(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_RosterUnreadMessage(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) return; + + 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_build(); + } + } + + nbuddy = g_list_find(buddylist, unread_ptr); + if (nbuddy) { + set_current_buddy(nbuddy); + if (chatmode) scr_ShowBuddyWindow(); + } else + scr_LogPrint(LPRINT_LOGNORM, "Error: nbuddy == NULL"); // should not happen +} + +// scr_RosterJumpAlternate() +// Try to jump to alternate (== previous) buddy +void scr_RosterJumpAlternate(void) +{ + if (!alternate_buddy || g_list_position(buddylist, alternate_buddy) == -1) + return; + set_current_buddy(alternate_buddy); + if (chatmode) + scr_ShowBuddyWindow(); +} + +// scr_RosterDisplay(filter) +// Set the roster filter mask. If filter is null/empty, the current +// mask is displayed. +void scr_RosterDisplay(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_build(); + update_roster = TRUE; + 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_BufferScrollUpDown() +// Scroll up/down the current buddy window, +// - half a screen if nblines is 0, +// - up if updown == -1, down if updown == 1 +void scr_BufferScrollUpDown(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_SearchWindow(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 + 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; + } + } + for (n=0 ; 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_UpdateWindow(win_entry); + + // Finished :) + update_panels(); +} + +// scr_BufferClear() +// Clear the current buddy window (used for the /clear command) +void scr_BufferClear(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_SearchWindow(CURRENT_JID, isspe); + if (!win_entry) return; + + win_entry->bd->cleared = TRUE; + win_entry->bd->top = NULL; + + // Refresh the window + scr_UpdateWindow(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. +static void buffer_purge(gpointer key, gpointer value, gpointer data) +{ + int *p_closebuf = data; + winbuf *win_entry = value; + + // Delete the current hbuf + hbuf_free(&win_entry->bd->hbuf); + + if (*p_closebuf) { + g_hash_table_remove(winbufhash, key); + } else { + win_entry->bd->cleared = FALSE; + win_entry->bd->top = NULL; + } +} + +// scr_BufferPurge(closebuf, jid) +// Purge/Drop the current buddy buffer or jid's buffer if jid != NULL. +// If closebuf is 1, close the buffer. +void scr_BufferPurge(int closebuf, const char *jid) +{ + winbuf *win_entry; + guint isspe; + guint *p_closebuf; + const char *cjid; + guint hold_chatmode = FALSE; + + if (jid) { + cjid = jid; + isspe = FALSE; + // 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_SearchWindow(cjid, isspe); + if (!win_entry) return; + + if (!isspe) { + p_closebuf = g_new(guint, 1); + *p_closebuf = closebuf; + buffer_purge((gpointer)cjid, win_entry, p_closebuf); + g_free(p_closebuf); + 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; + + win_entry->bd->cleared = FALSE; + win_entry->bd->top = NULL; + } + + // Refresh the window + scr_UpdateBuddyWindow(); + + // Finished :) + update_panels(); +} + +void scr_BufferPurgeAll(int closebuf) +{ + guint *p_closebuf; + p_closebuf = g_new(guint, 1); + + *p_closebuf = closebuf; + g_hash_table_foreach(winbufhash, buffer_purge, p_closebuf); + g_free(p_closebuf); + + if (closebuf) { + scr_set_chatmode(FALSE); + currentWindow = NULL; + } + + // Refresh the window + scr_UpdateBuddyWindow(); + + // Finished :) + update_panels(); +} + +// scr_BufferScrollLock(lock) +// Lock/unlock the current buddy buffer +// lock = 1 : lock +// lock = 0 : unlock +// lock = -1: toggle lock status +void scr_BufferScrollLock(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_SearchWindow(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; + //win_entry->bd->cleared = 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_ShowBuddyWindow() + // at least once. (Maybe it will cause a double refresh...) + if (!chatmode && !win_entry->bd->top) { + chatmode = TRUE; + scr_ShowBuddyWindow(); + chatmode = FALSE; + } + + // Refresh the window + scr_UpdateBuddyWindow(); + + // Finished :) + update_panels(); +} + +// scr_BufferTopBottom() +// Jump to the head/tail of the current buddy window +// (top if topbottom == -1, bottom topbottom == 1) +void scr_BufferTopBottom(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_SearchWindow(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_UpdateWindow(win_entry); + + // Finished :) + update_panels(); +} + +// scr_BufferSearch(direction, text) +// Jump to the next line containing text +// (backward search if direction == -1, forward if topbottom == 1) +void scr_BufferSearch(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_SearchWindow(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_UpdateWindow(win_entry); + + // Finished :) + update_panels(); + } else + scr_LogPrint(LPRINT_NORMAL, "Search string not found"); +} + +// scr_BufferPercent(n) +// Jump to the specified position in the buffer, in % +void scr_BufferPercent(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_SearchWindow(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_UpdateWindow(win_entry); + + // Finished :) + update_panels(); +} + +// scr_BufferDate(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_BufferDate(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_SearchWindow(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; + + // Refresh the window + scr_UpdateWindow(win_entry); + + // Finished :) + update_panels(); +} + +void scr_BufferDump(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)", key, + g_list_length(head), hbuf_get_blocks_number(head)); +} + +void scr_BufferList(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); + update_roster = TRUE; +} + +// scr_set_chatmode() +// Public function to (un)set chatmode... +inline void scr_set_chatmode(int enable) +{ + chatmode = enable; + scr_UpdateChatStatus(TRUE); +} + +// 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_SearchWindow(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_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 inline 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_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; +} + +// 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))) { + if ((c < prev_char(ptr_inputline, inputLine)) && (!iswalnum(get_char(c)))) + c = next_char(c); + } + + // Modify the line + ptr_inputline = c; + for (;;) { + *c = *old++; + if (!*c++) break; + } + 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) + *ptr_inputline = towupper(get_char(ptr_inputline)); + else + *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) { + *ptr_inputline = towupper(get_char(ptr_inputline)); + upcased = 1; + } else *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. +int readline_accept_line(int down_history) +{ + scr_CheckAutoAway(TRUE); + if (process_line(inputLine)) + return 255; + // 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; + } + return 0; +} + +void readline_cancel_completion(void) +{ + scr_cancel_current_completion(); + scr_end_current_completion(); + check_offset(-1); +} + +void readline_do_completion(void) +{ + int i, n; + + if (multimode != 2) { + // Not in verbatim multi-line mode + scr_handle_tab(); + } 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_CheckAutoAway(TRUE); + ParseColors(); + scr_Resize(); + redrawwin(stdscr); +} + +void readline_disable_chat_mode(guint show_roster) +{ + scr_CheckAutoAway(TRUE); + currentWindow = NULL; + chatmode = FALSE; + if (current_buddy) + buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE); + if (show_roster) + scr_RosterVisibility(1); + scr_UpdateChatStatus(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; + for ( ; *src ; ) + *c++ = *src++; + *c = 0; + check_offset(-1); +} + +void readline_forward_kill_char(void) +{ + if (!*ptr_inputline) + return; + + strcpy(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) +{ + strcpy(inputLine, ptr_inputline); + ptr_inputline = inputLine; + 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); +} + +// 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... +static void scr_handle_tab(void) +{ + 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 = 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); + 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); + 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(); + if (cchar) + scr_insert_text(cchar); + completion_started = TRUE; + } + } else { // Completion already initialized + scr_cancel_current_completion(); + // Now complete again + cchar = complete(); + if (cchar) + scr_insert_text(cchar); + } +} + +static void scr_cancel_current_completion(void) +{ + char *c; + char *src = ptr_inputline; + guint back = cancel_completion(); + guint i; + // Remove $back chars + for (i = 0; i < back; i++) + ptr_inputline = prev_char(ptr_inputline, inputLine); + c = ptr_inputline; + for ( ; *src ; ) + *c++ = *src++; + *c = 0; +} + +static void scr_end_current_completion(void) +{ + done_completion(); + completion_started = FALSE; +} + +// check_offset(int direction) +// Check inputline_offset value, and make sure the cursor is inside the +// screen. +static inline void check_offset(int direction) +{ + int i; + char *c = &inputLine[inputline_offset]; + // Left side + if (inputline_offset && direction <= 0) { + while (ptr_inputline <= c) { + for (i = 0; i < 5; i++) + c = prev_char(c, inputLine); + if (c == inputLine) + break; + } + } + // Right side + if (direction >= 0) { + int delta = get_char_width(c); + while (ptr_inputline > c) { + c = next_char(c); + delta += get_char_width(c); + } + c = &inputLine[inputline_offset]; + while (delta >= maxX) { + for (i = 0; i < 5; i++) { + delta -= get_char_width(c); + c = next_char(c); + } + } + } + inputline_offset = c - inputLine; +} + +#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) +// prints inputLine with underlined words when misspelled +static inline void print_checked_line(void) +{ + char *wprint_char_fmt = "%c"; + int point; + int nrchar = maxX; + char *ptrCur = inputLine + inputline_offset; + +#ifdef UNICODE + // We need this to display a single UTF-8 char... Any better solution? + if (utf8_mode) + wprint_char_fmt = "%lc"; +#endif + + wmove(inputWnd, 0, 0); // problem with backspace + + while (*ptrCur && nrchar-- > 0) { + point = ptrCur - inputLine; + if (maskLine[point]) + wattrset(inputWnd, A_UNDERLINE); + wprintw(inputWnd, wprint_char_fmt, get_char(ptrCur)); + wattrset(inputWnd, A_NORMAL); + ptrCur = next_char(ptrCur); + } +} +#endif + +static inline void refresh_inputline(void) +{ +#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) + if (settings_opt_get_int("spell_enable")) { + memset(maskLine, 0, INPUTLINE_LENGTH+1); + spellcheck(inputLine, maskLine); + } + print_checked_line(); + wclrtoeol(inputWnd); + if (*ptr_inputline) { + // hack to set cursor pos. Characters can have different width, + // so I know of no better way. + char c = *ptr_inputline; + *ptr_inputline = 0; + print_checked_line(); + *ptr_inputline = c; + } +#else + mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset); + wclrtoeol(inputWnd); + if (*ptr_inputline) { + // hack to set cursor pos. Characters can have different width, + // so I know of no better way. + char c = *ptr_inputline; + *ptr_inputline = 0; + mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset); + *ptr_inputline = c; + } +#endif +} + +void scr_handle_CtrlC(void) +{ + if (!Curses) return; + // Leave multi-line mode + process_command(mkcmdstr("msay abort"), TRUE); + // Same as Ctrl-g, now + scr_cancel_current_completion(); + scr_end_current_completion(); + check_offset(-1); + refresh_inputline(); +} + +static void add_keyseq(char *seqstr, guint mkeycode, gint value) +{ + keyseq *ks; + + // Let's make sure the length is correct + if (strlen(seqstr) > MAX_KEYSEQ_LENGTH) { + scr_LogPrint(LPRINT_LOGNORM, "add_keyseq(): key sequence is too long!"); + return; + } + + ks = g_new0(keyseq, 1); + ks->seqstr = g_strdup(seqstr); + ks->mkeycode = mkeycode; + ks->value = value; + keyseqlist = g_slist_append(keyseqlist, ks); +} + +// match_keyseq(iseq, &ret) +// Check if "iseq" is a known key escape sequence. +// Return value: +// -1 if "seq" matches no known sequence +// 0 if "seq" could match 1 or more known sequences +// >0 if "seq" matches a key sequence; the mkey code is returned +// and *ret is set to the matching keyseq structure. +static inline gint match_keyseq(int *iseq, keyseq **ret) +{ + GSList *ksl; + keyseq *ksp; + char *p, c; + int *i; + int needmore = FALSE; + + for (ksl = keyseqlist; ksl; ksl = g_slist_next(ksl)) { + ksp = ksl->data; + p = ksp->seqstr; + i = iseq; + while (1) { + c = (unsigned char)*i; + if (!*p && !c) { // Match + (*ret) = ksp; + return ksp->mkeycode; + } + if (!c) { + // iseq is too short + needmore = TRUE; + break; + } else if (!*p || c != *p) { + // This isn't a match + break; + } + p++; i++; + } + } + + if (needmore) + return 0; + return -1; +} + +static inline int match_utf8_keyseq(int *iseq) +{ + int *strp = iseq; + unsigned c = *strp++; + unsigned mask = 0x80; + int len = -1; + while (c & mask) { + mask >>= 1; + len++; + } + if (len <= 0 || len > 4) + return -1; + c &= mask - 1; + while ((*strp & 0xc0) == 0x80) { + if (len-- <= 0) // can't happen + return -1; + c = (c << 6) | (*strp++ & 0x3f); + } + if (len) + return 0; + return c; +} + +void scr_Getch(keycode *kcode) +{ + keyseq *mks = NULL; + int ks[MAX_KEYSEQ_LENGTH+1]; + int i; + + memset(kcode, 0, sizeof(keycode)); + memset(ks, 0, sizeof(ks)); + + kcode->value = wgetch(inputWnd); + if (utf8_mode) { + bool ismeta = (kcode->value == 27); +#ifdef NCURSES_MOUSE_VERSION + bool ismouse = (kcode->value == KEY_MOUSE); + + if (ismouse) { + MEVENT mouse; + getmouse(&mouse); + kcode->value = mouse.bstate; + kcode->mcode = MKEY_MOUSE; + return; + } else if (ismeta) +#else + if (ismeta) +#endif + ks[0] = wgetch(inputWnd); + else + ks[0] = kcode->value; + + for (i = 0; i < MAX_KEYSEQ_LENGTH - 1; i++) { + int match = match_utf8_keyseq(ks); + if (match == -1) + break; + if (match > 0) { + kcode->value = match; + kcode->utf8 = 1; + if (ismeta) + kcode->mcode = MKEY_META; + return; + } + ks[i + 1] = wgetch(inputWnd); + if (ks[i + 1] == ERR) + break; + } + while (i > 0) + ungetch(ks[i--]); + if (ismeta) + ungetch(ks[0]); + memset(ks, 0, sizeof(ks)); + } + if (kcode->value != 27) + return; + + // Check for escape key sequence + for (i=0; i < MAX_KEYSEQ_LENGTH; i++) { + int match; + ks[i] = wgetch(inputWnd); + if (ks[i] == ERR) break; + match = match_keyseq(ks, &mks); + if (match == -1) { + // No such key sequence. Let's increment i as it is a valid key. + i++; + break; + } + if (match > 0) { + // We have a matching sequence + kcode->mcode = mks->mkeycode; + kcode->value = mks->value; + return; + } + } + + // No match. Let's return a meta-key. + if (i > 0) { + kcode->mcode = MKEY_META; + kcode->value = ks[0]; + } + if (i > 1) { + // We need to push some keys back to the keyboard buffer + while (i-- > 1) + ungetch(ks[i]); + } + return; +} + +void scr_DoUpdate(void) +{ + doupdate(); +} + +static int 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_CheckAutoAway(TRUE); + if (process_command(cmdline, TRUE)) + return 255; // Quit + g_free(cmdline); + return 0; + } + + 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 + return -1; +} + +// process_key(key) +// Handle the pressed key, in the command line (bottom). +void process_key(keycode kcode) +{ + int key = kcode.value; + int display_char = FALSE; + + lock_chatstate = FALSE; + + switch (kcode.mcode) { + case 0: + break; + case MKEY_EQUIV: + key = kcode.value; + break; + case MKEY_META: + default: + if (bindcommand(kcode) == 255) { + mcabber_set_terminate_ui(); + return; + } + key = ERR; // Do not process any further + } + + if (kcode.utf8) { + if (key != ERR && !kcode.mcode) + display_char = TRUE; + goto display; + } + + switch (key) { + case 0: + case ERR: + break; + case 9: // Tab + readline_do_completion(); + break; + case 13: // Enter + if (readline_accept_line(FALSE) == 255) { + mcabber_set_terminate_ui(); + return; + } + break; + case 3: // Ctrl-C + scr_handle_CtrlC(); + break; + case KEY_RESIZE: +#ifdef USE_SIGWINCH + { + struct winsize size; + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) != -1) + resizeterm(size.ws_row, size.ws_col); + } +#endif + 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) == 255)) { + mcabber_set_terminate_ui(); + return; + } + } + } + + if (completion_started && key != 9 && key != KEY_RESIZE) + scr_end_current_completion(); + refresh_inputline(); + + if (!lock_chatstate) { + // Set chat state to composing (1) if the user is currently composing, + // i.e. not an empty line and not a command line. + if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR) + set_chatstate(0); + else + set_chatstate(1); + if (chatstate) + time(&chatstate_timestamp); + } + return; +} + +#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) +// initialization +void spellcheck_init(void) +{ + int spell_enable = settings_opt_get_int("spell_enable"); + const char *spell_lang = settings_opt_get("spell_lang"); +#ifdef WITH_ASPELL + const char *spell_encoding = settings_opt_get("spell_encoding"); + AspellCanHaveError *possible_err; +#endif + + if (!spell_enable) + return; + +#ifdef WITH_ENCHANT + if (spell_checker) { + enchant_broker_free_dict(spell_broker, spell_checker); + enchant_broker_free(spell_broker); + spell_checker = NULL; + spell_broker = NULL; + } + + spell_broker = enchant_broker_init(); + spell_checker = enchant_broker_request_dict(spell_broker, spell_lang); +#endif +#ifdef WITH_ASPELL + if (spell_checker) { + delete_aspell_speller(spell_checker); + delete_aspell_config(spell_config); + spell_checker = NULL; + spell_config = NULL; + } + + spell_config = new_aspell_config(); + aspell_config_replace(spell_config, "encoding", spell_encoding); + aspell_config_replace(spell_config, "lang", spell_lang); + possible_err = new_aspell_speller(spell_config); + + if (aspell_error_number(possible_err) != 0) { + spell_checker = NULL; + delete_aspell_config(spell_config); + spell_config = NULL; + } else { + spell_checker = to_aspell_speller(possible_err); + } +#endif +} + +// Deinitialization of spellchecker +void spellcheck_deinit(void) +{ + if (spell_checker) { +#ifdef WITH_ENCHANT + enchant_broker_free_dict(spell_broker, spell_checker); +#endif +#ifdef WITH_ASPELL + delete_aspell_speller(spell_checker); +#endif + spell_checker = NULL; + } + +#ifdef WITH_ENCHANT + if (spell_broker) { + enchant_broker_free(spell_broker); + spell_broker = NULL; + } +#endif +#ifdef WITH_ASPELL + if (spell_config) { + delete_aspell_config(spell_config); + spell_config = NULL; + } +#endif +} + +#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; + + if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR) + 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); + + if (spell_checker && +#ifdef WITH_ENCHANT + enchant_dict_check(spell_checker, start, line - start) != 0 +#endif +#ifdef WITH_ASPELL + aspell_speller_check(spell_checker, start, line - start) == 0 +#endif + ) + memset(&checked[start - line_start], SPELLBADCHAR, line - start); + } +} +#endif + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */