/* X-Chat
* Copyright (C) 1998 Peter Zelezny.
*
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#ifdef WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
#define GLIB_DISABLE_DEPRECATION_WARNINGS
#include "fe-gtk.h"
#include "../common/hexchat.h"
#include "../common/hexchatc.h"
#include "../common/cfgfiles.h"
#include "../common/fe.h"
#include "../common/userlist.h"
#include "../common/outbound.h"
#include "../common/util.h"
#include "../common/text.h"
#include "../common/plugin.h"
#include "../common/typedef.h"
#include <gdk/gdkkeysyms.h>
#include "gtkutil.h"
#include "menu.h"
#include "xtext.h"
#include "palette.h"
#include "maingui.h"
#include "textgui.h"
#include "fkeys.h"
static void replace_handle (GtkWidget * wid);
void key_action_tab_clean (void);
/***************** Key Binding Code ******************/
/* NOTES:
To add a new action:
1) inc KEY_MAX_ACTIONS
2) write the function at the bottom of this file (with all the others)
FIXME: Write about calling and returning
3) Add it to key_actions
--AGL
*/
/* Remember that the *number* of actions is this *plus* 1 --AGL */
#define KEY_MAX_ACTIONS 14
struct key_binding
{
int keyval; /* GDK keynumber */
char *keyname; /* String with the name of the function */
int action; /* Index into key_actions */
int mod; /* Flags of STATE_* above */
char *data1, *data2; /* Pointers to strings, these must be freed */
struct key_binding *next;
};
struct key_action
{
int (*handler) (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
struct session * sess);
char *name;
char *help;
};
struct gcomp_data
{
char data[CHANLEN];
int elen;
};
static int key_load_kbs ();
static void key_save_kbs ();
static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2,
struct session *sess);
static int key_action_page_switch (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2, struct session *sess);
int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
struct session *sess);
static int key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2, struct session *sess);
static int key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2, struct session *sess);
static int key_action_history_up (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2, struct session *sess);
static int key_action_history_down (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2, struct session *sess);
static int key_action_tab_comp (GtkWidget * wid, GdkEventKey * evt, char *d1,
char *d2, struct session *sess);
static int key_action_comp_chng (GtkWidget * wid, GdkEventKey * evt, char *d1,
char *d2, struct session *sess);
static int key_action_replace (GtkWidget * wid, GdkEventKey * evt, char *d1,
char *d2, struct session *sess);
static int key_action_move_tab_left (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2,
struct session *sess);
static int key_action_move_tab_right (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2,
struct session *sess);
static int key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2,
struct session *sess);
static int key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2,
struct session *sess);
static int key_action_put_history (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2,
struct session *sess);
static GtkWidget *key_dialog;
static struct key_binding *keys_root = NULL;
static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
{key_action_handle_command, "Run Command",
N_("The \002Run Command\002 action runs the data in Data 1 as if it had been typed into the entry box where you pressed the key sequence. Thus it can contain text (which will be sent to the channel/person), commands or user commands. When run all \002\\n\002 characters in Data 1 are used to deliminate separate commands so it is possible to run more than one command. If you want a \002\\\002 in the actual text run then enter \002\\\\\002")},
{key_action_page_switch, "Change Page",
N_("The \002Change Page\002 command switches between pages in the notebook. Set Data 1 to the page you want to switch to. If Data 2 is set to anything then the switch will be relative to the current position. Set Data 1 to auto to switch to the page with the most recent and important activity (queries first, then channels with hilight, channels with dialogue, channels with other data)")},
{key_action_insert, "Insert in Buffer",
N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position")},
{key_action_scroll_page, "Scroll Page",
N_("The \002Scroll Page\002 command scrolls the text widget up or down one page or one line. Set Data 1 to either Top, Bottom, Up, Down, +1 or -1.")},
{key_action_set_buffer, "Set Buffer",
N_("The \002Set Buffer\002 command sets the entry where the key sequence was entered to the contents of Data 1")},
{key_action_history_up, "Last Command",
N_("The \002Last Command\002 command sets the entry to contain the last command entered - the same as pressing up in a shell")},
{key_action_history_down, "Next Command",
N_("The \002Next Command\002 command sets the entry to contain the next command entered - the same as pressing down in a shell")},
{key_action_tab_comp, "Complete nick/command",
N_("This command changes the text in the entry to finish an incomplete nickname or command. If Data 1 is set then double-tabbing in a string will select the last nick, not the next")},
{key_action_comp_chng, "Change Selected Nick",
N_("This command scrolls up and down through the list of nicks. If Data 1 is set to anything it will scroll up, else it scrolls down")},
{key_action_replace, "Check For Replace",
N_("This command checks the last word entered in the entry against the replace list and replaces it if it finds a match")},
{key_action_move_tab_left, "Move front tab left",
N_("This command moves the front tab left by one")},
{key_action_move_tab_right, "Move front tab right",
N_("This command moves the front tab right by one")},
{key_action_move_tab_family_left, "Move tab family left",
N_("This command moves the current tab family to the left")},
{key_action_move_tab_family_right, "Move tab family right",
N_("This command moves the current tab family to the right")},
{key_action_put_history, "Push input line into history",
N_("Push input line into history but doesn't send to server")},
};
#define default_kb_cfg \
"C\nPrior\nChange Page\nD1:-1\nD2:Relative\n\n"\
"C\nNext\nChange Page\nD1:1\nD2:Relative\n\n"\
"A\n9\nChange Page\nD1:9\nD2!\n\n"\
"A\n8\nChange Page\nD1:8\nD2!\n\n"\
"A\n7\nChange Page\nD1:7\nD2!\n\n"\
"A\n6\nChange Page\nD1:6\nD2!\n\n"\
"A\n5\nChange Page\nD1:5\nD2!\n\n"\
"A\n4\nChange Page\nD1:4\nD2!\n\n"\
"A\n3\nChange Page\nD1:3\nD2!\n\n"\
"A\n2\nChange Page\nD1:2\nD2!\n\n"\
"A\n1\nChange Page\nD1:1\nD2!\n\n"\
"A\ngrave\nChange Page\nD1:auto\nD2!\n\n"\
"C\no\nInsert in Buffer\nD1:\nD2!\n\n"\
"C\nb\nInsert in Buffer\nD1:\nD2!\n\n"\
"C\nk\nInsert in Buffer\nD1:\nD2!\n\n"\
"C\ni\nInsert in Buffer\nD1:\nD2!\n\n"\
"C\nu\nInsert in Buffer\nD1:\nD2!\n\n"\
"S\nNext\nChange Selected Nick\nD1!\nD2!\n\n"\
"S\nPrior\nChange Selected Nick\nD1:Up\nD2!\n\n"\
"None\nNext\nScroll Page\nD1:Down\nD2!\n\n"\
"C\nHome\nScroll Page\nD1:Top\nD2!\n\n"\
"C\nEnd\nScroll Page\nD1:Bottom\nD2!\n\n"\
"None\nPrior\nScroll Page\nD1:Up\nD2!\n\n"\
"S\nDown\nScroll Page\nD1:+1\nD2!\n\n"\
"S\nUp\nScroll Page\nD1:-1\nD2!\n\n"\
"None\nDown\nNext Command\nD1!\nD2!\n\n"\
"None\nUp\nLast Command\nD1!\nD2!\n\n"\
"None\nTab\nComplete nick/command\nD1!\nD2!\n\n"\
"None\nspace\nCheck For Replace\nD1!\nD2!\n\n"\
"None\nReturn\nCheck For Replace\nD1!\nD2!\n\n"\
"None\nKP_Enter\nCheck For Replace\nD1!\nD2!\n\n"\
"C\nTab\nComplete nick/command\nD1:Up\nD2!\n\n"\
"A\nLeft\nMove front tab left\nD1!\nD2!\n\n"\
"A\nRight\nMove front tab right\nD1!\nD2!\n\n"\
"CS\nPrior\nMove tab family left\nD1!\nD2!\n\n"\
"CS\nNext\nMove tab family right\nD1!\nD2!\n\n"\
"None\nF9\nRun Command\nD1:/GUI MENU TOGGLE\nD2!\n\n"
void
key_init ()
{
keys_root = NULL;
if (key_load_kbs () == 1)
{
fe_message (_("There was an error loading key"
" bindings configuration"), FE_MSG_ERROR);
}
}
static char *
key_get_key_name (int keyval)
{
return gdk_keyval_name (gdk_keyval_to_lower (keyval));
}
/* Ok, here are the NOTES
key_handle_key_press now handles all the key presses and history_keypress is
now defunct. It goes thru the linked list keys_root and finds a matching
key. It runs the action func and switches on these values:
0) Return
1) Find next
2) stop signal and return
* history_keypress is now dead (and gone)
* key_handle_key_press now takes its role
* All the possible actions are in a struct called key_actions (in fkeys.c)
* it is made of {function, name, desc}
* key bindings can pass 2 *text* strings to the handler. If more options are nee
ded a format can be put on one of these options
* key actions are passed {
the entry widget
the Gdk event
data 1
data 2
session struct
}
* key bindings are stored in a linked list of key_binding structs
* which looks like {
int keyval; GDK keynumber
char *keyname; String with the name of the function
int action; Index into key_actions
int mod; Flags of STATE_* above
char *data1, *data2; Pointers to strings, these must be freed
struct key_binding *next;
}
* remember that is (data1 || data2) != NULL then they need to be free()'ed
--AGL
*/
gboolean
key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess)
{
struct key_binding *kb, *last = NULL;
int keyval = evt->keyval;
int mod, n;
GSList *list;
/* where did this event come from? */
list = sess_list;
while (list)
{
sess = list->data;
if (sess->gui->input_box == wid)
{
if (sess->gui->is_tab)
sess = current_tab;
break;
}
list = list->next;
}
if (!list)
return FALSE;
current_sess = sess;
if (plugin_emit_keypress (sess, evt->state, evt->keyval, evt->length, evt->string))
return 1;
/* maybe the plugin closed this tab? */
if (!is_session (sess))
return 1;
mod = evt->state & (STATE_CTRL | STATE_ALT | STATE_SHIFT);
kb = keys_root;
while (kb)
{
if (kb->keyval == keyval && kb->mod == mod)
{
if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS)
return 0;
/* Bump this binding to the top of the list */
if (last != NULL)
{
last->next = kb->next;
kb->next = keys_root;
keys_root = kb;
}
/* Run the function */
n = key_actions[kb->action].handler (wid, evt, kb->data1,
kb->data2, sess);
switch (n)
{
case 0:
return 1;
case 2:
g_signal_stop_emission_by_name (G_OBJECT (wid),
"key_press_event");
return 1;
}
}
last = kb;
kb = kb->next;
}
switch (keyval)
{
case GDK_KEY_space:
key_action_tab_clean ();
break;
}
return 0;
}
/* Walks keys_root and free()'s everything */
/*static void
key_free_all ()
{
struct key_binding *cur, *next;
cur = keys_root;
while (cur)
{
next = cur->next;
if (cur->data1)
free (cur->data1);
if (cur->data2)
free (cur->data2);
free (cur);
cur = next;
}
keys_root = NULL;
}*/
/* Turns mod flags into a C-A-S string */
static char *
key_make_mod_str (int mod, char *buf)
{
int i = 0;
if (mod & STATE_CTRL)
{
if (i)
buf[i++] = '-';
buf[i++] = 'C';
}
if (mod & STATE_ALT)
{
if (i)
buf[i++] = '-';
buf[i++] = 'A';
}
if (mod & STATE_SHIFT)
{
if (i)
buf[i++] = '-';
buf[i++] = 'S';
}
buf[i] = 0;
return buf;
}
/* ***** GUI code here ******************* */
/* NOTE: The key_dialog defin is above --AGL */
static GtkWidget *key_dialog_act_menu, *key_dialog_kb_clist;
static GtkWidget *key_dialog_tog_c, *key_dialog_tog_s, *key_dialog_tog_a;
static GtkWidget *key_dialog_ent_key, *key_dialog_ent_d1, *key_dialog_ent_d2;
static GtkWidget *key_dialog_text;
static void
key_dialog_close ()
{
key_dialog = NULL;
key_save_kbs ();
}
static void
key_dialog_add_new (GtkWidget * button, GtkCList * list)
{
gchar *strs[] = { "", NULL, NULL, NULL, NULL };
struct key_binding *kb;
strs[1] = _("<none>");
strs[2] = _("<none>");
strs[3] = _("<none>");
strs[4] = _("<none>");
kb = malloc (sizeof (struct key_binding));
kb->keyval = 0;
kb->keyname = NULL;
kb->action = -1;
kb->mod = 0;
kb->data1 = kb->data2 = NULL;
kb->next = keys_root;
keys_root = kb;
gtk_clist_set_row_data (GTK_CLIST (list),
gtk_clist_append (GTK_CLIST (list), strs), kb);
}
static void
key_dialog_delete (GtkWidget * button, GtkCList * list)
{
struct key_binding *kb, *cur, *last;
int row = gtkutil_clist_selection ((GtkWidget *) list);
if (row != -1)
{
kb = gtk_clist_get_row_data (list, row);
cur = keys_root;
last = NULL;
while (cur)
{
if (cur == kb)
{
if (last)
last->next = kb->next;
else
keys_root = kb->next;
if (kb->data1)
free (kb->data1);
if (kb->data2)
free (kb->data2);
free (kb);
gtk_clist_remove (list, row);
return;
}
last = cur;
cur = cur->next;
}
printf ("*** key_dialog_delete: couldn't find kb in list!\n");
/*if (getenv ("HEXCHAT_DEBUG"))
abort ();*/
}
}
static void
key_print_text (GtkXText *xtext, char *text)
{
unsigned int old = prefs.hex_stamp_text;
prefs.hex_stamp_text = 0; /* temporarily disable stamps */
gtk_xtext_clear (GTK_XTEXT (xtext)->buffer, 0);
PrintTextRaw (GTK_XTEXT (xtext)->buffer, text, 0, 0);
prefs.hex_stamp_text = old;
}
static void
key_dialog_sel_act (GtkWidget * un, int num)
{
int row = gtkutil_clist_selection (key_dialog_kb_clist);
struct key_binding *kb;
if (row != -1)
{
kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row);
kb->action = num;
gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 2,
_(key_actions[num].name));
if (key_actions[num].help)
{
key_print_text (GTK_XTEXT (key_dialog_text), _(key_actions[num].help));
}
}
}
static void
key_dialog_sel_row (GtkWidget * clist, gint row, gint column,
GdkEventButton * evt, gpointer data)
{
struct key_binding *kb = gtk_clist_get_row_data (GTK_CLIST (clist), row);
if (kb == NULL)
{
printf ("*** key_dialog_sel_row: kb == NULL\n");
abort ();
}
if (kb->action > -1 && kb->action <= KEY_MAX_ACTIONS)
{
gtk_option_menu_set_history (GTK_OPTION_MENU (key_dialog_act_menu),
kb->action);
if (key_actions[kb->action].help)
{
key_print_text (GTK_XTEXT (key_dialog_text), _(key_actions[kb->action].help));
}
}
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_c),
(kb->mod & STATE_CTRL) == STATE_CTRL);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_s),
(kb->mod & STATE_SHIFT) == STATE_SHIFT);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_a),
(kb->mod & STATE_ALT) == STATE_ALT);
if (kb->data1)
gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d1), kb->data1);
else
gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d1), "");
if (kb->data2)
gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d2), kb->data2);
else
gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d2), "");
if (kb->keyname)
gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_key), kb->keyname);
else
gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_key), "");
}
static void
key_dialog_tog_key (GtkWidget * tog, int kstate)
{
int state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (tog));
int row = gtkutil_clist_selection (key_dialog_kb_clist);
struct key_binding *kb;
char buf[32];
if (row == -1)
return;
kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row);
if (state)
kb->mod |= kstate;
else
kb->mod &= ~kstate;
gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 0,
key_make_mod_str (kb->mod, buf));
}
static GtkWidget *
key_dialog_make_toggle (char *label, void *callback, void *option,
GtkWidget * box)
{
GtkWidget *wid;
wid = gtk_check_button_new_with_label (label);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), 0);
g_signal_connect (G_OBJECT (wid), "toggled",
G_CALLBACK (callback), option);
gtk_box_pack_end (GTK_BOX (box), wid, 0, 0, 0);
gtk_widget_show (wid);
return wid;
}
static GtkWidget *
key_dialog_make_entry (char *label, char *act, void *callback, void *option,
GtkWidget * box)
{
GtkWidget *wid, *hbox;;
hbox = gtk_hbox_new (0, 2);
if (label)
{
wid = gtk_label_new (label);
gtk_widget_show (wid);
gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
}
wid = gtk_entry_new ();
if (act)
{
g_signal_connect (G_OBJECT (wid), act, G_CALLBACK (callback),
option);
}
gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0);
gtk_widget_show (wid);
gtk_widget_show (hbox);
gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0);
return wid;
}
static void
key_dialog_set_key (GtkWidget * entry, GdkEventKey * evt, void *none)
{
int row = gtkutil_clist_selection (key_dialog_kb_clist);
struct key_binding *kb;
gtk_entry_set_text (GTK_ENTRY (entry), "");
if (row == -1)
return;
kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row);
kb->keyval = evt->keyval;
kb->keyname = key_get_key_name (kb->keyval);
gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 1, kb->keyname);
gtk_entry_set_text (GTK_ENTRY (entry), kb->keyname);
g_signal_stop_emission_by_name (G_OBJECT (entry), "key_press_event");
}
static void
key_dialog_set_data (GtkWidget * entry, int d)
{
const char *text = gtk_entry_get_text (GTK_ENTRY (entry));
int row = gtkutil_clist_selection (key_dialog_kb_clist);
struct key_binding *kb;
char *buf;
int len = strlen (text);
len++;
if (row == -1)
return;
kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row);
if (d == 0)
{ /* using data1 */
if (kb->data1)
free (kb->data1);
buf = (char *) malloc (len);
memcpy (buf, text, len);
kb->data1 = buf;
gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 3, text);
} else
{
if (kb->data2)
free (kb->data2);
buf = (char *) malloc (len);
memcpy (buf, text, len);
kb->data2 = buf;
gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 4, text);
}
}
void
key_dialog_show ()
{
GtkWidget *vbox, *hbox, *list, *vbox2, *wid, *wid2, *wid3;
struct key_binding *kb;
gchar *titles[] = { NULL, NULL, NULL, "1", "2" };
char temp[32];
int i;
titles[0] = _("Mod");
titles[1] = _("Key");
titles[2] = _("Action");
if (key_dialog)
{
mg_bring_tofront (key_dialog);
return;
}
key_dialog =
mg_create_generic_tab ("editkeys", _(DISPLAY_NAME": Keyboard Shortcuts"),
TRUE, FALSE, key_dialog_close, NULL, 560, 330, &vbox, 0);
hbox = gtk_hbox_new (0, 2);
gtk_box_pack_start (GTK_BOX (vbox), hbox, 1, 1, 0);
list = gtkutil_clist_new (5, titles, hbox, 0, key_dialog_sel_row, 0, NULL,
0, GTK_SELECTION_SINGLE);
gtk_widget_set_usize (list, 400, 0);
key_dialog_kb_clist = list;
gtk_widget_show (hbox);
kb = keys_root;
gtk_clist_set_column_width (GTK_CLIST (list), 1, 50);
gtk_clist_set_column_width (GTK_CLIST (list), 2, 120);
gtk_clist_set_column_width (GTK_CLIST (list), 3, 50);
gtk_clist_set_column_width (GTK_CLIST (list), 4, 50);
while (kb)
{
titles[0] = key_make_mod_str (kb->mod, temp);
titles[1] = kb->keyname;
if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS)
titles[2] = _("<none>");
else
titles[2] = key_actions[kb->action].name;
if (kb->data1)
titles[3] = kb->data1;
else
titles[3] = _("<none>");
if (kb->data2)
titles[4] = kb->data2;
else
titles[4] = _("<none>");
gtk_clist_set_row_data (GTK_CLIST (list),
gtk_clist_append (GTK_CLIST (list), titles),
kb);
kb = kb->next;
}
vbox2 = gtk_vbox_new (0, 2);
gtk_box_pack_end (GTK_BOX (hbox), vbox2, 1, 1, 0);
wid = gtk_button_new_with_label (_("Add New"));
gtk_box_pack_start (GTK_BOX (vbox2), wid, 0, 0, 0);
g_signal_connect (G_OBJECT (wid), "clicked",
G_CALLBACK (key_dialog_add_new), list);
gtk_widget_show (wid);
wid = gtk_button_new_with_label (_("Delete"));
gtk_box_pack_start (GTK_BOX (vbox2), wid, 0, 0, 0);
g_signal_connect (G_OBJECT (wid), "clicked",
G_CALLBACK (key_dialog_delete), list);
gtk_widget_show (wid);
gtk_widget_show (vbox2);
wid = gtk_option_menu_new ();
wid2 = gtk_menu_new ();
for (i = 0; i <= KEY_MAX_ACTIONS; i++)
{
wid3 = gtk_menu_item_new_with_label (_(key_actions[i].name));
gtk_widget_show (wid3);
gtk_menu_shell_append (GTK_MENU_SHELL (wid2), wid3);
g_signal_connect (G_OBJECT (wid3), "activate",
G_CALLBACK (key_dialog_sel_act),
GINT_TO_POINTER (i));
}
gtk_option_menu_set_menu (GTK_OPTION_MENU (wid), wid2);
gtk_option_menu_set_history (GTK_OPTION_MENU (wid), 0);
gtk_box_pack_end (GTK_BOX (vbox2), wid, 0, 0, 0);
gtk_widget_show (wid);
key_dialog_act_menu = wid;
key_dialog_tog_s = key_dialog_make_toggle (_("Shift"), key_dialog_tog_key,
(void *) STATE_SHIFT, vbox2);
key_dialog_tog_a = key_dialog_make_toggle (_("Alt"), key_dialog_tog_key,
(void *) STATE_ALT, vbox2);
key_dialog_tog_c = key_dialog_make_toggle (_("Ctrl"), key_dialog_tog_key,
(void *) STATE_CTRL, vbox2);
key_dialog_ent_key = key_dialog_make_entry (_("Key"), "key_press_event",
key_dialog_set_key, NULL,
vbox2);
key_dialog_ent_d1 = key_dialog_make_entry (_("Data 1"), "activate",
key_dialog_set_data, NULL,
vbox2);
key_dialog_ent_d2 = key_dialog_make_entry (_("Data 2"), "activate",
key_dialog_set_data,
(void *) 1, vbox2);
wid2 = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (wid2), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_container_add (GTK_CONTAINER (vbox), wid2);
key_dialog_text = gtk_xtext_new (colors, 0);
gtk_container_add (GTK_CONTAINER (wid2), key_dialog_text);
gtk_xtext_set_font (GTK_XTEXT (key_dialog_text), prefs.hex_text_font);
gtk_widget_show_all (key_dialog);
}
static void
key_save_kbs (void)
{
int fd, i;
char buf[512];
struct key_binding *kb;
fd = hexchat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY,
0x180, XOF_DOMODE);
if (fd < 0)
{
fe_message (_("Error opening keys config file\n"), FE_MSG_ERROR);
return;
}
write (fd, buf,
snprintf (buf, 510, "# HexChat key bindings config file\n\n"));
kb = keys_root;
i = 0;
while (kb)
{
if (kb->keyval == -1 || kb->keyname == NULL || kb->action < 0)
{
kb = kb->next;
continue;
}
i = 0;
if (kb->mod & STATE_CTRL)
{
i++;
write (fd, "C", 1);
}
if (kb->mod & STATE_ALT)
{
i++;
write (fd, "A", 1);
}
if (kb->mod & STATE_SHIFT)
{
i++;
write (fd, "S", 1);
}
if (i == 0)
write (fd, "None\n", 5);
else
write (fd, "\n", 1);
write (fd, buf, snprintf (buf, 510, "%s\n%s\n", kb->keyname,
key_actions[kb->action].name));
if (kb->data1 && kb->data1[0])
write (fd, buf, snprintf (buf, 510, "D1:%s\n", kb->data1));
else
write (fd, "D1!\n", 4);
if (kb->data2 && kb->data2[0])
write (fd, buf, snprintf (buf, 510, "D2:%s\n", kb->data2));
else
write (fd, "D2!\n", 4);
write (fd, "\n", 1);
kb = kb->next;
}
close (fd);
}
/* I just know this is going to be a nasty parse, if you think it's bugged
it almost certainly is so contact the HexChat dev team --AGL */
static inline int
key_load_kbs_helper_mod (char *in, int *out)
{
int n, len, mod = 0;
/* First strip off the fluff */
while (in[0] == ' ' || in[0] == '\t')
in++;
len = strlen (in);
while (in[len] == ' ' || in[len] == '\t')
{
in[len] = 0;
len--;
}
if (strcmp (in, "None") == 0)
{
*out = 0;
return 0;
}
for (n = 0; n < len; n++)
{
switch (in[n])
{
case 'C':
mod |= STATE_CTRL;
break;
case 'A':
mod |= STATE_ALT;
break;
case 'S':
mod |= STATE_SHIFT;
break;
default:
return 1;
}
}
*out = mod;
return 0;
}
/* These are just local defines to keep me sane --AGL */
#define KBSTATE_MOD 0
#define KBSTATE_KEY 1
#define KBSTATE_ACT 2
#define KBSTATE_DT1 3
#define KBSTATE_DT2 4
/* *** Warning, Warning! - massive function ahead! --AGL */
static int
key_load_kbs (void)
{
char *buf, *ibuf;
struct stat st;
struct key_binding *kb = NULL, *last = NULL;
int fd, len, pnt = 0, state = 0, n;
off_t size;
fd = hexchat_open_file ("keybindings.conf", O_RDONLY, 0, 0);
if (fd < 0)
{
ibuf = strdup (default_kb_cfg);
size = strlen (default_kb_cfg);
}
else
{
if (fstat (fd, &st) != 0)
{
close (fd);
return 1;
}
ibuf = malloc (st.st_size);
read (fd, ibuf, st.st_size);
size = st.st_size;
close (fd);
}
while (buf_get_line (ibuf, &buf, &pnt, size))
{
if (buf[0] == '#')
continue;
if (strlen (buf) == 0)
continue;
switch (state)
{
case KBSTATE_MOD:
kb = (struct key_binding *) malloc (sizeof (struct key_binding));
if (key_load_kbs_helper_mod (buf, &kb->mod))
goto corrupt_file;
state = KBSTATE_KEY;
continue;
case KBSTATE_KEY:
/* First strip off the fluff */
while (buf[0] == ' ' || buf[0] == '\t')
buf++;
len = strlen (buf);
while (buf[len] == ' ' || buf[len] == '\t')
{
buf[len] = 0;
len--;
}
n = gdk_keyval_from_name (buf);
if (n == 0)
{
/* Unknown keyname, abort */
if (last)
last->next = NULL;
free (ibuf);
ibuf = malloc (1024);
snprintf (ibuf, 1024,
_("Unknown keyname %s in key bindings config file\nLoad aborted, please fix %s" G_DIR_SEPARATOR_S "keybindings.conf\n"),
buf, get_xdir ());
fe_message (ibuf, FE_MSG_ERROR);
free (ibuf);
return 2;
}
kb->keyname = gdk_keyval_name (n);
kb->keyval = n;
state = KBSTATE_ACT;
continue;
case KBSTATE_ACT:
/* First strip off the fluff */
while (buf[0] == ' ' || buf[0] == '\t')
buf++;
len = strlen (buf);
while (buf[len] == ' ' || buf[len] == '\t')
{
buf[len] = 0;
len--;
}
for (n = 0; n < KEY_MAX_ACTIONS + 1; n++)
{
if (strcmp (key_actions[n].name, buf) == 0)
{
kb->action = n;
break;
}
}
if (n == KEY_MAX_ACTIONS + 1)
{
if (last)
last->next = NULL;
free (ibuf);
ibuf = malloc (1024);
snprintf (ibuf, 1024,
_("Unknown action %s in key bindings config file\nLoad aborted, Please fix %s" G_DIR_SEPARATOR_S "keybindings\n"),
buf, get_xdir ());
fe_message (ibuf, FE_MSG_ERROR);
free (ibuf);
return 3;
}
state = KBSTATE_DT1;
continue;
case KBSTATE_DT1:
case KBSTATE_DT2:
if (state == KBSTATE_DT1)
kb->data1 = kb->data2 = NULL;
while (buf[0] == ' ' || buf[0] == '\t')
buf++;
if (buf[0] != 'D')
{
free (ibuf);
ibuf = malloc (1024);
snprintf (ibuf, 1024,
_("Expecting Data line (beginning Dx{:|!}) but got:\n%s\n\nLoad aborted, Please fix %s" G_DIR_SEPARATOR_S "keybindings\n"),
buf, get_xdir ());
fe_message (ibuf, FE_MSG_ERROR);
free (ibuf);
return 4;
}
switch (buf[1])
{
case '1':
if (state != KBSTATE_DT1)
goto corrupt_file;
break;
case '2':
if (state != KBSTATE_DT2)
goto corrupt_file;
break;
default:
goto corrupt_file;
}
if (buf[2] == ':')
{
len = strlen (buf);
/* Add one for the NULL, subtract 3 for the "Dx:" */
len++;
len -= 3;
if (state == KBSTATE_DT1)
{
kb->data1 = malloc (len);
memcpy (kb->data1, &buf[3], len);
} else
{
kb->data2 = malloc (len);
memcpy (kb->data2, &buf[3], len);
}
} else if (buf[2] == '!')
{
if (state == KBSTATE_DT1)
kb->data1 = NULL;
else
kb->data2 = NULL;
}
if (state == KBSTATE_DT1)
{
state = KBSTATE_DT2;
continue;
} else
{
if (last)
last->next = kb;
else
keys_root = kb;
last = kb;
state = KBSTATE_MOD;
}
continue;
}
}
if (last)
last->next = NULL;
free (ibuf);
return 0;
corrupt_file:
/*if (getenv ("HEXCHAT_DEBUG"))
abort ();*/
snprintf (ibuf, 1024,
_("Key bindings config file is corrupt, load aborted\n"
"Please fix %s" G_DIR_SEPARATOR_S "keybindings.conf\n"),
get_xdir ());
fe_message (ibuf, FE_MSG_ERROR);
free (ibuf);
free (kb);
return 5;
}
/* ***** Key actions start here *********** */
/* See the NOTES above --AGL */
/* "Run command" */
static int
key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1,
char *d2, struct session *sess)
{
int ii, oi, len;
char out[2048], d = 0;
if (!d1)
return 0;
len = strlen (d1);
/* Replace each "\n" substring with '\n' */
for (ii = oi = 0; ii < len; ii++)
{
d = d1[ii];
if (d == '\\')
{
ii++;
d = d1[ii];
if (d == 'n')
out[oi++] = '\n';
else if (d == '\\')
out[oi++] = '\\';
else
{
out[oi++] = '\\';
out[oi++] = d;
}
continue;
}
out[oi++] = d;
}
out[oi] = 0;
handle_multiline (sess, out, 0, 0);
return 0;
}
/*
* Check if the given session is inside the main window. This predicate
* is passed to lastact_getfirst() as a way to filter out detached sessions.
* XXX: Consider moving this in a different file?
*/
static int
session_check_is_tab(session *sess)
{
if (!sess || !sess->gui)
return FALSE;
return (sess->gui->is_tab);
}
static int
key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1,
char *d2, struct session *sess)
{
session *newsess;
int len, i, num;
if (!d1)
return 1;
len = strlen (d1);
if (!len)
return 1;
if (strcasecmp(d1, "auto") == 0)
{
/* Auto switch makes no sense in detached sessions */
if (!sess->gui->is_tab)
return 1;
/* Obtain a session with recent activity */
newsess = lastact_getfirst(session_check_is_tab);
if (newsess)
{
/*
* Only sessions in the current window should be considered (i.e.
* we don't want to move the focus on a different window). This
* call could, in theory, do this, but we checked before that
* newsess->gui->is_tab and sess->gui->is_tab.
*/
mg_bring_tofront_sess(newsess);
return 0;
}
else
return 1;
}
for (i = 0; i < len; i++)
{
if (d1[i] < '0' || d1[i] > '9')
{
if (i == 0 && (d1[i] == '+' || d1[i] == '-'))
continue;
else
return 1;
}
}
num = atoi (d1);
if (!d2)
num--;
if (!d2 || d2[0] == 0)
mg_switch_page (FALSE, num);
else
mg_switch_page (TRUE, num);
return 0;
}
int
key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
struct session *sess)
{
int tmp_pos;
if (!d1)
return 1;
tmp_pos = SPELL_ENTRY_GET_POS (wid);
SPELL_ENTRY_INSERT (wid, d1, strlen (d1), &tmp_pos);
SPELL_ENTRY_SET_POS (wid, tmp_pos);
return 2;
}
/* handles PageUp/Down keys */
static int
key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt, char *d1,
char *d2, struct session *sess)
{
int value, end;
GtkAdjustment *adj;
enum scroll_type { PAGE_TOP, PAGE_BOTTOM, PAGE_UP, PAGE_DOWN, LINE_UP, LINE_DOWN };
int type = PAGE_DOWN;
if (d1)
{
if (!g_ascii_strcasecmp (d1, "top"))
type = PAGE_TOP;
else if (!g_ascii_strcasecmp (d1, "bottom"))
type = PAGE_BOTTOM;
else if (!g_ascii_strcasecmp (d1, "up"))
type = PAGE_UP;
else if (!g_ascii_strcasecmp (d1, "down"))
type = PAGE_DOWN;
else if (!g_ascii_strcasecmp (d1, "+1"))
type = LINE_DOWN;
else if (!g_ascii_strcasecmp (d1, "-1"))
type = LINE_UP;
}
if (!sess)
return 0;
adj = gtk_range_get_adjustment (GTK_RANGE (sess->gui->vscrollbar));
end = gtk_adjustment_get_upper (adj) - gtk_adjustment_get_lower (adj) - gtk_adjustment_get_page_size (adj);
switch (type)
{
case PAGE_TOP:
value = 0;
break;
case PAGE_BOTTOM:
value = end;
break;
case PAGE_UP:
value = gtk_adjustment_get_value (adj) - (gtk_adjustment_get_page_size (adj) - 1);
break;
case PAGE_DOWN:
value = gtk_adjustment_get_value (adj) + (gtk_adjustment_get_page_size (adj) - 1);
break;
case LINE_UP:
value = gtk_adjustment_get_value (adj) - 1.0;
break;
case LINE_DOWN:
value = gtk_adjustment_get_value (adj) + 1.0;
break;
}
if (value < 0)
value = 0;
if (value > end)
value = end;
gtk_adjustment_set_value (adj, value);
return 0;
}
static int
key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
struct session *sess)
{
if (!d1)
return 1;
if (d1[0] == 0)
return 1;
SPELL_ENTRY_SET_TEXT (wid, d1);
SPELL_ENTRY_SET_POS (wid, -1);
return 2;
}
static int
key_action_history_up (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
struct session *sess)
{
char *new_line;
new_line = history_up (&sess->history, SPELL_ENTRY_GET_TEXT (wid));
if (new_line)
{
SPELL_ENTRY_SET_TEXT (wid, new_line);
SPELL_ENTRY_SET_POS (wid, -1);
}
return 2;
}
static int
key_action_history_down (GtkWidget * wid, GdkEventKey * ent, char *d1,
char *d2, struct session *sess)
{
char *new_line;
new_line = history_down (&sess->history);
if (new_line)
{
SPELL_ENTRY_SET_TEXT (wid, new_line);
SPELL_ENTRY_SET_POS (wid, -1);
}
return 2;
}
/* old data that we reuse */
static struct gcomp_data old_gcomp;
/* work on the data, ie return only channels */
static int
double_chan_cb (session *lsess, GList **list)
{
if (lsess->type == SESS_CHANNEL)
*list = g_list_prepend(*list, lsess->channel);
return TRUE;
}
/* convert a slist -> list. */
static GList *
chanlist_double_list (GSList *inlist)
{
GList *list = NULL;
g_slist_foreach(inlist, (GFunc)double_chan_cb, &list);
return list;
}
/* handle commands */
static int
double_cmd_cb (struct popup *pop, GList **list)
{
*list = g_list_prepend(*list, pop->name);
return TRUE;
}
/* convert a slist -> list. */
static GList *
cmdlist_double_list (GSList *inlist)
{
GList *list = NULL;
g_slist_foreach (inlist, (GFunc)double_cmd_cb, &list);
return list;
}
static char *
gcomp_nick_func (char *data)
{
if (data)
return ((struct User *)data)->nick;
return "";
}
void
key_action_tab_clean(void)
{
if (old_gcomp.elen)
{
old_gcomp.data[0] = 0;
old_gcomp.elen = 0;
}
}
/* Used in the followig completers */
#define COMP_BUF 2048
/* For use in sorting the user list for completion */
static int
talked_recent_cmp (struct User *a, struct User *b)
{
if (a->lasttalk < b->lasttalk)
return -1;
if (a->lasttalk > b->lasttalk)
return 1;
return 0;
}
static int
key_action_tab_comp (GtkWidget *t, GdkEventKey *entry, char *d1, char *d2,
struct session *sess)
{
int len = 0, elen = 0, i = 0, cursor_pos, ent_start = 0, comp = 0, found = 0,
prefix_len, skip_len = 0, is_nick, is_cmd = 0;
char buf[COMP_BUF], ent[CHANLEN], *postfix = NULL, *result, *ch;
GList *list = NULL, *tmp_list = NULL;
const char *text;
GCompletion *gcomp = NULL;
/* force the IM Context to reset */
SPELL_ENTRY_SET_EDITABLE (t, FALSE);
SPELL_ENTRY_SET_EDITABLE (t, TRUE);
text = SPELL_ENTRY_GET_TEXT (t);
if (text[0] == 0)
return 1;
len = g_utf8_strlen (text, -1); /* must be null terminated */
cursor_pos = SPELL_ENTRY_GET_POS (t);
buf[0] = 0; /* make sure we don't get garbage in the buffer */
/* handle "nick: " or "nick " or "#channel "*/
ch = g_utf8_find_prev_char(text, g_utf8_offset_to_pointer(text,cursor_pos));
if (ch && ch[0] == ' ')
{
skip_len++;
ch = g_utf8_find_prev_char(text, ch);
if (!ch)
return 2;
cursor_pos = g_utf8_pointer_to_offset(text, ch);
if (cursor_pos && (g_utf8_get_char_validated(ch, -1) == ':' ||
g_utf8_get_char_validated(ch, -1) == ',' ||
g_utf8_get_char_validated (ch, -1) == g_utf8_get_char_validated (prefs.hex_completion_suffix, -1)))
{
skip_len++;
}
else
cursor_pos = g_utf8_pointer_to_offset(text, g_utf8_offset_to_pointer(ch, 1));
}
comp = skip_len;
/* store the text following the cursor for reinsertion later */
if ((cursor_pos + skip_len) < len)
postfix = g_utf8_offset_to_pointer(text, cursor_pos + skip_len);
for (ent_start = cursor_pos; ; --ent_start)
{
if (ent_start == 0)
break;
ch = g_utf8_offset_to_pointer(text, ent_start - 1);
if (ch && ch[0] == ' ')
break;
}
if (ent_start == 0 && text[0] == prefs.hex_input_command_char[0])
{
ent_start++;
is_cmd = 1;
}
prefix_len = ent_start;
elen = cursor_pos - ent_start;
g_utf8_strncpy (ent, g_utf8_offset_to_pointer (text, prefix_len), elen);
is_nick = (ent[0] == '#' || ent[0] == '&' || is_cmd) ? 0 : 1;
if (sess->type == SESS_DIALOG && is_nick)
{
/* tab in a dialog completes the other person's name */
if (rfc_ncasecmp (sess->channel, ent, elen) == 0)
{
result = sess->channel;
is_nick = 0;
}
else
return 2;
}
else
{
if (is_nick)
{
gcomp = g_completion_new((GCompletionFunc)gcomp_nick_func);
tmp_list = userlist_double_list(sess); /* create a temp list so we can free the memory */
if (prefs.hex_completion_sort == 1) /* sort in last-talk order? */
tmp_list = g_list_sort (tmp_list, (void *)talked_recent_cmp);
}
else
{
gcomp = g_completion_new (NULL);
if (is_cmd)
{
tmp_list = cmdlist_double_list (command_list);
for(i = 0; xc_cmds[i].name != NULL ; i++)
{
tmp_list = g_list_prepend (tmp_list, xc_cmds[i].name);
}
tmp_list = plugin_command_list(tmp_list);
}
else
tmp_list = chanlist_double_list (sess_list);
}
tmp_list = g_list_reverse(tmp_list); /* make the comp entries turn up in the right order */
g_completion_set_compare (gcomp, (GCompletionStrncmpFunc)rfc_ncasecmp);
if (tmp_list)
{
g_completion_add_items (gcomp, tmp_list);
g_list_free (tmp_list);
}
if (comp && !(rfc_ncasecmp(old_gcomp.data, ent, old_gcomp.elen) == 0))
{
key_action_tab_clean ();
comp = 0;
}
list = g_completion_complete_utf8 (gcomp, comp ? old_gcomp.data : ent, &result);
if (result == NULL) /* No matches found */
{
g_completion_free(gcomp);
return 2;
}
if (comp) /* existing completion */
{
while(list) /* find the current entry */
{
if(rfc_ncasecmp(list->data, ent, elen) == 0)
{
found = 1;
break;
}
list = list->next;
}
if (found)
{
if (!(d1 && d1[0])) /* not holding down shift */
{
if (g_list_next(list) == NULL)
list = g_list_first(list);
else
list = g_list_next(list);
}
else
{
if (g_list_previous(list) == NULL)
list = g_list_last(list);
else
list = g_list_previous(list);
}
g_free(result);
result = (char*)list->data;
}
else
{
g_free(result);
g_completion_free(gcomp);
return 2;
}
}
else
{
strcpy(old_gcomp.data, ent);
old_gcomp.elen = elen;
/* Get the first nick and put out the data for future nickcompletes */
if (prefs.hex_completion_amount && g_list_length (list) <= prefs.hex_completion_amount)
{
g_free(result);
result = (char*)list->data;
}
else
{
/* bash style completion */
if (g_list_next(list) != NULL)
{
if (strlen (result) > elen) /* the largest common prefix is larger than nick, change the data */
{
if (prefix_len)
g_utf8_strncpy (buf, text, prefix_len);
strncat (buf, result, COMP_BUF - prefix_len);
cursor_pos = strlen (buf);
g_free(result);
if (postfix)
{
strcat (buf, " ");
strncat (buf, postfix, COMP_BUF - cursor_pos -1);
}
SPELL_ENTRY_SET_TEXT (t, buf);
SPELL_ENTRY_SET_POS (t, g_utf8_pointer_to_offset(buf, buf + cursor_pos));
buf[0] = 0;
}
else
g_free(result);
while (list)
{
len = strlen (buf); /* current buffer */
elen = strlen (list->data); /* next item to add */
if (len + elen + 2 >= COMP_BUF) /* +2 is space + null */
{
PrintText (sess, buf);
buf[0] = 0;
len = 0;
}
strcpy (buf + len, (char *) list->data);
strcpy (buf + len + elen, " ");
list = list->next;
}
PrintText (sess, buf);
g_completion_free(gcomp);
return 2;
}
/* Only one matching entry */
g_free(result);
result = list->data;
}
}
}
if(result)
{
if (prefix_len)
g_utf8_strncpy(buf, text, prefix_len);
strncat (buf, result, COMP_BUF - (prefix_len + 3)); /* make sure nicksuffix and space fits */
if(!prefix_len && is_nick)
strcat (buf, &prefs.hex_completion_suffix[0]);
strcat (buf, " ");
cursor_pos = strlen (buf);
if (postfix)
strncat (buf, postfix, COMP_BUF - cursor_pos - 2);
SPELL_ENTRY_SET_TEXT (t, buf);
SPELL_ENTRY_SET_POS (t, g_utf8_pointer_to_offset(buf, buf + cursor_pos));
}
if (gcomp)
g_completion_free(gcomp);
return 2;
}
#undef COMP_BUF
static int
key_action_comp_chng (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
struct session *sess)
{
key_action_tab_comp(wid, ent, d1, d2, sess);
return 2;
}
static int
key_action_replace (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
struct session *sess)
{
replace_handle (wid);
return 1;
}
static int
key_action_move_tab_left (GtkWidget * wid, GdkEventKey * ent, char *d1,
char *d2, struct session *sess)
{
mg_move_tab (sess, +1);
return 2; /* don't allow default action */
}
static int
key_action_move_tab_right (GtkWidget * wid, GdkEventKey * ent, char *d1,
char *d2, struct session *sess)
{
mg_move_tab (sess, -1);
return 2; /* -''- */
}
static int
key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * ent, char *d1,
char *d2, struct session *sess)
{
mg_move_tab_family (sess, +1);
return 2; /* don't allow default action */
}
static int
key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * ent, char *d1,
char *d2, struct session *sess)
{
mg_move_tab_family (sess, -1);
return 2; /* -''- */
}
static int
key_action_put_history (GtkWidget * wid, GdkEventKey * ent, char *d1,
char *d2, struct session *sess)
{
history_add (&sess->history, SPELL_ENTRY_GET_TEXT (wid));
SPELL_ENTRY_SET_TEXT (wid, "");
return 2; /* -''- */
}
/* -------- */
static void
replace_handle (GtkWidget *t)
{
const char *text, *postfix_pnt;
struct popup *pop;
GSList *list = replace_list;
char word[256];
char postfix[256];
char outbuf[4096];
int c, len, xlen;
text = SPELL_ENTRY_GET_TEXT (t);
len = strlen (text);
if (len < 1)
return;
for (c = len - 1; c > 0; c--)
{
if (text[c] == ' ')
break;
}
if (text[c] == ' ')
c++;
xlen = c;
if (len - c >= (sizeof (word) - 12))
return;
if (len - c < 1)
return;
memcpy (word, &text[c], len - c);
word[len - c] = 0;
len = strlen (word);
if (word[0] == '\'' && word[len] == '\'')
return;
postfix_pnt = NULL;
for (c = 0; c < len; c++)
{
if (word[c] == '\'')
{
postfix_pnt = &word[c + 1];
word[c] = 0;
break;
}
}
if (postfix_pnt != NULL)
{
if (strlen (postfix_pnt) > sizeof (postfix) - 12)
return;
strcpy (postfix, postfix_pnt);
}
while (list)
{
pop = (struct popup *) list->data;
if (strcmp (pop->name, word) == 0)
{
memcpy (outbuf, text, xlen);
outbuf[xlen] = 0;
if (postfix_pnt == NULL)
snprintf (word, sizeof (word), "%s", pop->cmd);
else
snprintf (word, sizeof (word), "%s%s", pop->cmd, postfix);
strcat (outbuf, word);
SPELL_ENTRY_SET_TEXT (t, outbuf);
SPELL_ENTRY_SET_POS (t, -1);
return;
}
list = list->next;
}
}