diff options
Diffstat (limited to 'src/fe-gtk/fkeys.c')
-rw-r--r-- | src/fe-gtk/fkeys.c | 1814 |
1 files changed, 1814 insertions, 0 deletions
diff --git a/src/fe-gtk/fkeys.c b/src/fe-gtk/fkeys.c new file mode 100644 index 00000000..014b5cc3 --- /dev/null +++ b/src/fe-gtk/fkeys.c @@ -0,0 +1,1814 @@ +/* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <ctype.h> + +#include "fe-gtk.h" + +#include <gtk/gtklabel.h> +#include <gtk/gtkeditable.h> +#include <gtk/gtkmenu.h> +#include <gtk/gtkmenuitem.h> +#include <gtk/gtkoptionmenu.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkclist.h> +#include <gtk/gtknotebook.h> +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkvscrollbar.h> + +#include "../common/xchat.h" +#include "../common/xchatc.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 <gdk/gdkkeysyms.h> +#include "gtkutil.h" +#include "menu.h" +#include "xtext.h" +#include "palette.h" +#include "maingui.h" +#include "textgui.h" +#include "fkeys.h" + +#ifdef USE_GTKSPELL +#include <gtk/gtktextview.h> +#endif + +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 +/* These are cp'ed from history.c --AGL */ +#define STATE_SHIFT GDK_SHIFT_MASK +#define STATE_ALT GDK_MOD1_MASK +#define STATE_CTRL GDK_CONTROL_MASK + +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 (char *); +static void key_load_defaults (); +static void key_save_kbs (char *); +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 has 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 seperate 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")}, + {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 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")}, +}; + +void +key_init () +{ + keys_root = NULL; + if (key_load_kbs (NULL) == 1) + { + key_load_defaults (); + if (key_load_kbs (NULL) == 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_space: + key_action_tab_clean (); + break; + +#if defined(USE_GTKSPELL) && !defined(WIN32) + /* gtktextview has no 'activate' event, so we trap ENTER here */ + case GDK_Return: + case GDK_KP_Enter: + if (!(evt->state & GDK_CONTROL_MASK)) + { + g_signal_stop_emission_by_name (G_OBJECT (wid), "key_press_event"); + mg_inputbox_cb (wid, sess->gui); + } +#endif + } + + 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_load_defaults () +{ + /* This is the default config */ +#define defcfg \ + "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"\ + "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"\ + "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"\ + "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" + int fd; + + fd = xchat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY, 0x180, XOF_DOMODE); + if (fd < 0) + /* ???!!! */ + return; + + write (fd, defcfg, strlen (defcfg)); + close (fd); +} + +static void +key_dialog_close () +{ + key_dialog = NULL; + key_save_kbs (NULL); +} + +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 ("XCHAT_DEBUG")) + abort ();*/ + } +} + +static void +key_print_text (GtkXText *xtext, char *text) +{ + unsigned int old = prefs.timestamp; + prefs.timestamp = 0; /* temporarily disable stamps */ + gtk_xtext_clear (GTK_XTEXT (xtext)->buffer, 0); + PrintTextRaw (GTK_XTEXT (xtext)->buffer, text, 0, 0); + prefs.timestamp = 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 (tog)->active; + 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); + gtk_signal_connect (GTK_OBJECT (wid), "toggled", + GTK_SIGNAL_FUNC (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) + { + gtk_signal_connect (GTK_OBJECT (wid), act, GTK_SIGNAL_FUNC (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, *hbox2; + 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", _("XChat: 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); + gtk_signal_connect (GTK_OBJECT (wid), "clicked", + GTK_SIGNAL_FUNC (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); + gtk_signal_connect (GTK_OBJECT (wid), "clicked", + GTK_SIGNAL_FUNC (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); + gtk_signal_connect (GTK_OBJECT (wid3), "activate", + GTK_SIGNAL_FUNC (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); + + hbox2 = gtk_hbox_new (0, 2); + gtk_box_pack_end (GTK_BOX (vbox), hbox2, 0, 0, 1); + + wid = gtk_xtext_new (colors, 0); + gtk_xtext_set_tint (GTK_XTEXT (wid), prefs.tint_red, prefs.tint_green, prefs.tint_blue); + gtk_xtext_set_background (GTK_XTEXT (wid), + channelwin_pix, + prefs.transparent); + gtk_widget_set_usize (wid, 0, 75); + gtk_box_pack_start (GTK_BOX (hbox2), wid, 1, 1, 1); + gtk_xtext_set_font (GTK_XTEXT (wid), prefs.font_normal); + gtk_widget_show (wid); + + wid2 = gtk_vscrollbar_new (GTK_XTEXT (wid)->adj); + gtk_box_pack_start (GTK_BOX (hbox2), wid2, 0, 0, 0); + gtk_widget_show (wid2); + + gtk_widget_show (hbox2); + key_dialog_text = wid; + + gtk_widget_show_all (key_dialog); +} + +static void +key_save_kbs (char *fn) +{ + int fd, i; + char buf[512]; + struct key_binding *kb; + + if (!fn) + fd = xchat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY, + 0x180, XOF_DOMODE); + else + fd = xchat_open_file (fn, O_CREAT | O_TRUNC | O_WRONLY, + 0x180, XOF_DOMODE | XOF_FULLPATH); + if (fd < 0) + { + fe_message (_("Error opening keys config file\n"), FE_MSG_ERROR); + return; + } + write (fd, buf, + snprintf (buf, 510, "# XChat 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 XChat 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 (char *filename) +{ + char *buf, *ibuf; + struct stat st; + struct key_binding *kb = NULL, *last = NULL; + int fd, len, pnt = 0, state = 0, n; + + if (filename == NULL) + fd = xchat_open_file ("keybindings.conf", O_RDONLY, 0, 0); + else + fd = xchat_open_file (filename, O_RDONLY, 0, XOF_FULLPATH); + if (fd < 0) + return 1; + if (fstat (fd, &st) != 0) + return 1; + ibuf = malloc (st.st_size); + read (fd, ibuf, st.st_size); + close (fd); + + while (buf_get_line (ibuf, &buf, &pnt, st.st_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/keybindings.conf\n"), + buf, get_xdir_utf8 ()); + 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/keybindings\n"), + buf, get_xdir_utf8 ()); + 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/keybindings\n"), + buf, get_xdir_utf8 ()); + 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 ("XCHAT_DEBUG")) + abort ();*/ + snprintf (ibuf, 1024, + _("Key bindings config file is corrupt, load aborted\n" + "Please fix %s/keybindings.conf\n"), + get_xdir_utf8 ()); + fe_message (ibuf, FE_MSG_ERROR); + free (ibuf); + 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; +} + +static int +key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1, + char *d2, struct session *sess) +{ + int len, i, num; + + if (!d1) + return 1; + + len = strlen (d1); + if (!len) + 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_UP, PAGE_DOWN, LINE_UP, LINE_DOWN }; + int type = PAGE_DOWN; + + if (d1) + { + if (!strcasecmp (d1, "up")) + type = PAGE_UP; + else if (!strcasecmp (d1, "+1")) + type = LINE_DOWN; + else if (!strcasecmp (d1, "-1")) + type = LINE_UP; + } + + if (!sess) + return 0; + + adj = GTK_RANGE (sess->gui->vscrollbar)->adjustment; + end = adj->upper - adj->lower - adj->page_size; + + switch (type) + { + case LINE_UP: + value = adj->value - 1.0; + break; + + case LINE_DOWN: + value = adj->value + 1.0; + break; + + case PAGE_UP: + value = adj->value - (adj->page_size - 1); + break; + + default: /* PAGE_DOWN */ + value = adj->value + (adj->page_size - 1); + 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) == prefs.nick_suffix[0])) + { + 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.cmdchar[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.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; + } + +#if GLIB_CHECK_VERSION(2,4,0) + list = g_completion_complete_utf8 (gcomp, comp ? old_gcomp.data : ent, &result); +#else + list = g_completion_complete (gcomp, comp ? old_gcomp.data : ent, &result); +#endif + + 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.completion_amount && g_list_length (list) <= prefs.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 !GLIB_CHECK_VERSION(2,4,0) + g_utf8_validate (buf, -1, (const gchar **)&result); + (*result) = 0; +#endif + 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.nick_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; /* -''- */ +} + + +/* -------- */ + + +#define STATE_SHIFT GDK_SHIFT_MASK +#define STATE_ALT GDK_MOD1_MASK +#define STATE_CTRL GDK_CONTROL_MASK + +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; + } +} + |