1 /* X-Chat
2 * Copyright (C) 1998 Peter Zelezny.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17 */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <string.h>
24 #include <fcntl.h>
25 #include <ctype.h>
26
27 #ifdef WIN32
28 #include <io.h>
29 #else
30 #include <unistd.h>
31 #endif
32
33 #define GLIB_DISABLE_DEPRECATION_WARNINGS
34 #include "fe-gtk.h"
35
36 #include "../common/hexchat.h"
37 #include "../common/hexchatc.h"
38 #include "../common/cfgfiles.h"
39 #include "../common/fe.h"
40 #include "../common/userlist.h"
41 #include "../common/outbound.h"
42 #include "../common/util.h"
43 #include "../common/text.h"
44 #include "../common/plugin.h"
45 #include "../common/typedef.h"
46 #include <gdk/gdkkeysyms.h>
47 #include "gtkutil.h"
48 #include "menu.h"
49 #include "xtext.h"
50 #include "palette.h"
51 #include "maingui.h"
52 #include "textgui.h"
53 #include "fkeys.h"
54
55 static void replace_handle (GtkWidget * wid);
56 void key_action_tab_clean (void);
57
58 /***************** Key Binding Code ******************/
59
60 /* NOTES:
61
62 To add a new action:
63 1) inc KEY_MAX_ACTIONS
64 2) write the function at the bottom of this file (with all the others)
65 FIXME: Write about calling and returning
66 3) Add it to key_actions
67
68 --AGL
69
70 */
71
72 /* Remember that the *number* of actions is this *plus* 1 --AGL */
73 #define KEY_MAX_ACTIONS 14
74
75 struct key_binding
76 {
77 guint keyval; /* keyval from gdk */
78 GdkModifierType mod; /* Modifier, always ran through key_modifier_get_valid() */
79 int action; /* Index into key_actions */
80 char *data1, *data2; /* Pointers to strings, these must be freed */
81 };
82
83 struct key_action
84 {
85 int (*handler) (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
86 struct session * sess);
87 char *name;
88 char *help;
89 };
90
91 struct gcomp_data
92 {
93 char data[CHANLEN];
94 int elen;
95 };
96
97 static int key_load_kbs (void);
98 static int key_save_kbs (void);
99 static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt,
100 char *d1, char *d2,
101 struct session *sess);
102 static int key_action_page_switch (GtkWidget * wid, GdkEventKey * evt,
103 char *d1, char *d2, struct session *sess);
104 int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
105 struct session *sess);
106 static int key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt,
107 char *d1, char *d2, struct session *sess);
108 static int key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt,
109 char *d1, char *d2, struct session *sess);
110 static int key_action_history_up (GtkWidget * wid, GdkEventKey * evt,
111 char *d1, char *d2, struct session *sess);
112 static int key_action_history_down (GtkWidget * wid, GdkEventKey * evt,
113 char *d1, char *d2, struct session *sess);
114 static int key_action_tab_comp (GtkWidget * wid, GdkEventKey * evt, char *d1,
115 char *d2, struct session *sess);
116 static int key_action_comp_chng (GtkWidget * wid, GdkEventKey * evt, char *d1,
117 char *d2, struct session *sess);
118 static int key_action_replace (GtkWidget * wid, GdkEventKey * evt, char *d1,
119 char *d2, struct session *sess);
120 static int key_action_move_tab_left (GtkWidget * wid, GdkEventKey * evt,
121 char *d1, char *d2,
122 struct session *sess);
123 static int key_action_move_tab_right (GtkWidget * wid, GdkEventKey * evt,
124 char *d1, char *d2,
125 struct session *sess);
126 static int key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * evt,
127 char *d1, char *d2,
128 struct session *sess);
129 static int key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * evt,
130 char *d1, char *d2,
131 struct session *sess);
132 static int key_action_put_history (GtkWidget * wid, GdkEventKey * evt,
133 char *d1, char *d2,
134 struct session *sess);
135
136 static GSList *keybind_list = NULL;
137
138 static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
139
140 {key_action_handle_command, "Run Command",
141 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")},
142 {key_action_page_switch, "Change Page",
143 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)")},
144 {key_action_insert, "Insert in Buffer",
145 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")},
146 {key_action_scroll_page, "Scroll Page",
147 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.")},
148 {key_action_set_buffer, "Set Buffer",
149 N_("The \002Set Buffer\002 command sets the entry where the key sequence was entered to the contents of Data 1")},
150 {key_action_history_up, "Last Command",
151 N_("The \002Last Command\002 command sets the entry to contain the last command entered - the same as pressing up in a shell")},
152 {key_action_history_down, "Next Command",
153 N_("The \002Next Command\002 command sets the entry to contain the next command entered - the same as pressing down in a shell")},
154 {key_action_tab_comp, "Complete nick/command",
155 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")},
156 {key_action_comp_chng, "Change Selected Nick",
157 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")},
158 {key_action_replace, "Check For Replace",
159 N_("This command checks the last word entered in the entry against the replace list and replaces it if it finds a match")},
160 {key_action_move_tab_left, "Move front tab left",
161 N_("This command moves the front tab left by one")},
162 {key_action_move_tab_right, "Move front tab right",
163 N_("This command moves the front tab right by one")},
164 {key_action_move_tab_family_left, "Move tab family left",
165 N_("This command moves the current tab family to the left")},
166 {key_action_move_tab_family_right, "Move tab family right",
167 N_("This command moves the current tab family to the right")},
168 {key_action_put_history, "Push input line into history",
169 N_("Push input line into history but doesn't send to server")},
170 };
171
172 #define default_kb_cfg \
173 "ACCEL=<Primary>Page_Up\nChange Page\nD1:-1\nD2:Relative\n\n"\
174 "ACCEL=<Primary>Page_Down\nChange Page\nD1:1\nD2:Relative\n\n"\
175 "ACCEL=<Alt>9\nChange Page\nD1:9\nD2!\n\n"\
176 "ACCEL=<Alt>8\nChange Page\nD1:8\nD2!\n\n"\
177 "ACCEL=<Alt>7\nChange Page\nD1:7\nD2!\n\n"\
178 "ACCEL=<Alt>6\nChange Page\nD1:6\nD2!\n\n"\
179 "ACCEL=<Alt>5\nChange Page\nD1:5\nD2!\n\n"\
180 "ACCEL=<Alt>4\nChange Page\nD1:4\nD2!\n\n"\
181 "ACCEL=<Alt>3\nChange Page\nD1:3\nD2!\n\n"\
182 "ACCEL=<Alt>2\nChange Page\nD1:2\nD2!\n\n"\
183 "ACCEL=<Alt>1\nChange Page\nD1:1\nD2!\n\n"\
184 "ACCEL=<Alt>grave\nChange Page\nD1:auto\nD2!\n\n"\
185 "ACCEL=<Primary>o\nInsert in Buffer\nD1:\017\nD2!\n\n"\
186 "ACCEL=<Primary>b\nInsert in Buffer\nD1:\002\nD2!\n\n"\
187 "ACCEL=<Primary>k\nInsert in Buffer\nD1:\003\nD2!\n\n"\
188 "ACCEL=<Primary>i\nInsert in Buffer\nD1:\035\nD2!\n\n"\
189 "ACCEL=<Primary>u\nInsert in Buffer\nD1:\037\nD2!\n\n"\
190 "ACCEL=<Primary>r\nInsert in Buffer\nD1:\026\nD2!\n\n"\
191 "ACCEL=<Shift>Page_Down\nChange Selected Nick\nD1!\nD2!\n\n"\
192 "ACCEL=<Shift>Page_Up\nChange Selected Nick\nD1:Up\nD2!\n\n"\
193 "ACCEL=Page_Down\nScroll Page\nD1:Down\nD2!\n\n"\
194 "ACCEL=<Primary>Home\nScroll Page\nD1:Top\nD2!\n\n"\
195 "ACCEL=<Primary>End\nScroll Page\nD1:Bottom\nD2!\n\n"\
196 "ACCEL=Page_Up\nScroll Page\nD1:Up\nD2!\n\n"\
197 "ACCEL=<Shift>Down\nScroll Page\nD1:+1\nD2!\n\n"\
198 "ACCEL=<Shift>Up\nScroll Page\nD1:-1\nD2!\n\n"\
199 "ACCEL=Down\nNext Command\nD1!\nD2!\n\n"\
200 "ACCEL=Up\nLast Command\nD1!\nD2!\n\n"\
201 "ACCEL=Tab\nComplete nick/command\nD1!\nD2!\n\n"\
202 "ACCEL=<Shift>ISO_Left_Tab\nComplete nick/command\nD1:Previous\nD2!\n\n"\
203 "ACCEL=space\nCheck For Replace\nD1!\nD2!\n\n"\
204 "ACCEL=Return\nCheck For Replace\nD1!\nD2!\n\n"\
205 "ACCEL=KP_Enter\nCheck For Replace\nD1!\nD2!\n\n"\
206 "ACCEL=<Primary>Tab\nComplete nick/command\nD1:Up\nD2!\n\n"\
207 "ACCEL=<Alt>Left\nMove front tab left\nD1!\nD2!\n\n"\
208 "ACCEL=<Alt>Right\nMove front tab right\nD1!\nD2!\n\n"\
209 "ACCEL=<Primary><Shift>Page_Up\nMove tab family left\nD1!\nD2!\n\n"\
210 "ACCEL=<Primary><Shift>Page_Down\nMove tab family right\nD1!\nD2!\n\n"\
211 "ACCEL=F9\nRun Command\nD1:/GUI MENU TOGGLE\nD2!\n\n"
212
213 void
key_init()214 key_init ()
215 {
216 if (key_load_kbs () == 1)
217 {
218 fe_message (_("There was an error loading key"
219 " bindings configuration"), FE_MSG_ERROR);
220 }
221 }
222
223 static inline int
key_get_action_from_string(char * text)224 key_get_action_from_string (char *text)
225 {
226 int i;
227
228 for (i = 0; i < KEY_MAX_ACTIONS + 1; i++)
229 {
230 if (strcmp (key_actions[i].name, text) == 0)
231 {
232 return i;
233 }
234 }
235
236 return 0;
237 }
238
239 static void
key_free(gpointer data)240 key_free (gpointer data)
241 {
242 struct key_binding *kb = (struct key_binding*)data;
243
244 g_return_if_fail (kb != NULL);
245
246 g_free (kb->data1);
247 g_free (kb->data2);
248 g_free (kb);
249 }
250
251 /* Ok, here are the NOTES
252
253 key_handle_key_press now handles all the key presses and history_keypress is
254 now defunct. It goes thru the linked list keys_root and finds a matching
255 key. It runs the action func and switches on these values:
256 0) Return
257 1) Find next
258 2) stop signal and return
259
260 * history_keypress is now dead (and gone)
261 * key_handle_key_press now takes its role
262 * All the possible actions are in a struct called key_actions (in fkeys.c)
263 * it is made of {function, name, desc}
264 * key bindings can pass 2 *text* strings to the handler. If more options are nee
265 ded a format can be put on one of these options
266 * key actions are passed {
267 the entry widget
268 the Gdk event
269 data 1
270 data 2
271 session struct
272 }
273 * key bindings are stored in a linked list of key_binding structs
274 * which looks like {
275 guint keyval; GDK keynumber
276 int action; Index into key_actions
277 GdkModiferType mod; modifier, only ones from key_modifer_get_valid()
278 char *data1, *data2; Pointers to strings, these must be freed
279 struct key_binding *next;
280 }
281 * remember that is (data1 || data2) != NULL then they need to be free()'ed
282
283 --AGL
284
285 */
286
287 static inline GdkModifierType
key_modifier_get_valid(GdkModifierType mod)288 key_modifier_get_valid (GdkModifierType mod)
289 {
290 GdkModifierType ret;
291
292 #ifdef __APPLE__
293 ret = mod & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK);
294 #else
295 /* These masks work on both Windows and Unix */
296 ret = mod & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK);
297 #endif
298
299 return ret;
300 }
301
302 gboolean
key_handle_key_press(GtkWidget * wid,GdkEventKey * evt,session * sess)303 key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess)
304 {
305 struct key_binding *kb;
306 int n;
307 GSList *list;
308
309 /* where did this event come from? */
310 list = sess_list;
311 while (list)
312 {
313 sess = list->data;
314 if (sess->gui->input_box == wid)
315 {
316 if (sess->gui->is_tab)
317 sess = current_tab;
318 break;
319 }
320 list = list->next;
321 }
322 if (!list)
323 return FALSE;
324 current_sess = sess;
325
326 if (plugin_emit_keypress (sess, evt->state, evt->keyval, gdk_keyval_to_unicode (evt->keyval)))
327 return 1;
328
329 /* maybe the plugin closed this tab? */
330 if (!is_session (sess))
331 return 1;
332
333 list = keybind_list;
334 while (list)
335 {
336 kb = (struct key_binding*)list->data;
337
338 if (kb->keyval == evt->keyval && kb->mod == key_modifier_get_valid (evt->state))
339 {
340 if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS)
341 return 0;
342
343 /* Run the function */
344 n = key_actions[kb->action].handler (wid, evt, kb->data1,
345 kb->data2, sess);
346 switch (n)
347 {
348 case 0:
349 return 1;
350 case 2:
351 g_signal_stop_emission_by_name (G_OBJECT (wid),
352 "key_press_event");
353 return 1;
354 }
355 }
356 list = g_slist_next (list);
357 }
358
359 switch (evt->keyval)
360 {
361 case GDK_KEY_space:
362 key_action_tab_clean ();
363 break;
364 }
365
366 return 0;
367 }
368
369
370 /* ***** GUI code here ******************* */
371
372 enum
373 {
374 KEY_COLUMN,
375 ACCEL_COLUMN,
376 ACTION_COLUMN,
377 D1_COLUMN,
378 D2_COLUMN,
379 N_COLUMNS
380 };
381
382 static GtkWidget *key_dialog = NULL;
383
384 static inline GtkTreeModel *
get_store(void)385 get_store (void)
386 {
387 return gtk_tree_view_get_model (g_object_get_data (G_OBJECT (key_dialog), "view"));
388 }
389
390 static void
key_dialog_print_text(GtkXText * xtext,char * text)391 key_dialog_print_text (GtkXText *xtext, char *text)
392 {
393 unsigned int old = prefs.hex_stamp_text;
394 prefs.hex_stamp_text = 0; /* temporarily disable stamps */
395 gtk_xtext_clear (GTK_XTEXT (xtext)->buffer, 0);
396 PrintTextRaw (GTK_XTEXT (xtext)->buffer, text, 0, 0);
397 prefs.hex_stamp_text = old;
398 }
399
400 static void
key_dialog_set_key(GtkCellRendererAccel * accel,gchar * pathstr,guint accel_key,GdkModifierType accel_mods,guint hardware_keycode,gpointer userdata)401 key_dialog_set_key (GtkCellRendererAccel *accel, gchar *pathstr, guint accel_key,
402 GdkModifierType accel_mods, guint hardware_keycode, gpointer userdata)
403 {
404 GtkTreeModel *model = get_store ();
405 GtkTreePath *path = gtk_tree_path_new_from_string (pathstr);
406 GtkTreeIter iter;
407 gchar *label_name, *accel_name;
408
409 /* Shift tab requires an exception, hopefully that list ends here.. */
410 if (accel_key == GDK_KEY_Tab && accel_mods & GDK_SHIFT_MASK)
411 accel_key = GDK_KEY_ISO_Left_Tab;
412
413 label_name = gtk_accelerator_get_label (accel_key, key_modifier_get_valid (accel_mods));
414 accel_name = gtk_accelerator_name (accel_key, key_modifier_get_valid (accel_mods));
415
416 gtk_tree_model_get_iter (model, &iter, path);
417 gtk_list_store_set (GTK_LIST_STORE (model), &iter, KEY_COLUMN, label_name,
418 ACCEL_COLUMN, accel_name, -1);
419
420 gtk_tree_path_free (path);
421 g_free (label_name);
422 g_free (accel_name);
423 }
424
425 static void
key_dialog_combo_changed(GtkCellRendererCombo * combo,gchar * pathstr,GtkTreeIter * new_iter,gpointer data)426 key_dialog_combo_changed (GtkCellRendererCombo *combo, gchar *pathstr,
427 GtkTreeIter *new_iter, gpointer data)
428 {
429 GtkTreeModel *model;
430 GtkXText *xtext;
431 gchar *actiontext = NULL;
432 gint action;
433
434 xtext = GTK_XTEXT (g_object_get_data (G_OBJECT (key_dialog), "xtext"));
435 model = GTK_TREE_MODEL (data);
436
437 gtk_tree_model_get (model, new_iter, 0, &actiontext, -1);
438
439 if (actiontext)
440 {
441 #ifdef WIN32
442 /* We need to manually update the store */
443 GtkTreePath *path;
444 GtkTreeIter iter;
445
446 path = gtk_tree_path_new_from_string (pathstr);
447 model = get_store ();
448
449 gtk_tree_model_get_iter (model, &iter, path);
450 gtk_list_store_set (GTK_LIST_STORE (model), &iter, ACTION_COLUMN, actiontext, -1);
451
452 gtk_tree_path_free (path);
453 #endif
454
455 action = key_get_action_from_string (actiontext);
456 key_dialog_print_text (xtext, key_actions[action].help);
457
458 g_free (actiontext);
459 }
460 }
461
462 static void
key_dialog_entry_edited(GtkCellRendererText * render,gchar * pathstr,gchar * new_text,gpointer data)463 key_dialog_entry_edited (GtkCellRendererText *render, gchar *pathstr, gchar *new_text, gpointer data)
464 {
465 GtkTreeModel *model = get_store ();
466 GtkTreePath *path = gtk_tree_path_new_from_string (pathstr);
467 GtkTreeIter iter;
468 gint column = GPOINTER_TO_INT (data);
469
470 gtk_tree_model_get_iter (model, &iter, path);
471 gtk_list_store_set (GTK_LIST_STORE (model), &iter, column, new_text, -1);
472
473 gtk_tree_path_free (path);
474 }
475
476 static gboolean
key_dialog_keypress(GtkWidget * wid,GdkEventKey * evt,gpointer userdata)477 key_dialog_keypress (GtkWidget *wid, GdkEventKey *evt, gpointer userdata)
478 {
479 GtkTreeView *view = g_object_get_data (G_OBJECT (key_dialog), "view");
480 GtkTreeModel *store;
481 GtkTreeIter iter1, iter2;
482 GtkTreeSelection *sel;
483 GtkTreePath *path;
484 gboolean handled = FALSE;
485 int delta;
486
487 if (evt->state & GDK_SHIFT_MASK)
488 {
489 if (evt->keyval == GDK_KEY_Up)
490 {
491 handled = TRUE;
492 delta = -1;
493 }
494 else if (evt->keyval == GDK_KEY_Down)
495 {
496 handled = TRUE;
497 delta = 1;
498 }
499 }
500
501 if (handled)
502 {
503 sel = gtk_tree_view_get_selection (view);
504 gtk_tree_selection_get_selected (sel, &store, &iter1);
505 path = gtk_tree_model_get_path (store, &iter1);
506 if (delta == 1)
507 gtk_tree_path_next (path);
508 else
509 gtk_tree_path_prev (path);
510 gtk_tree_model_get_iter (store, &iter2, path);
511 gtk_tree_path_free (path);
512 gtk_list_store_swap (GTK_LIST_STORE (store), &iter1, &iter2);
513 }
514
515 return handled;
516 }
517
518 static void
key_dialog_selection_changed(GtkTreeSelection * sel,gpointer userdata)519 key_dialog_selection_changed (GtkTreeSelection *sel, gpointer userdata)
520 {
521 GtkTreeModel *model;
522 GtkTreeIter iter;
523 GtkXText *xtext;
524 char *actiontext;
525 int action;
526
527 if (!gtk_tree_selection_get_selected (sel, &model, &iter) || model == NULL)
528 return;
529
530 xtext = GTK_XTEXT (g_object_get_data (G_OBJECT (key_dialog), "xtext"));
531 gtk_tree_model_get (model, &iter, ACTION_COLUMN, &actiontext, -1);
532
533 if (actiontext)
534 {
535 action = key_get_action_from_string (actiontext);
536 key_dialog_print_text (xtext, key_actions[action].help);
537 g_free (actiontext);
538 }
539 else
540 key_dialog_print_text (xtext, _("Select a row to get help information on its Action."));
541 }
542
543 static void
key_dialog_close(GtkWidget * wid,gpointer userdata)544 key_dialog_close (GtkWidget *wid, gpointer userdata)
545 {
546 gtk_widget_destroy (key_dialog);
547 key_dialog = NULL;
548 }
549
550 static void
key_dialog_save(GtkWidget * wid,gpointer userdata)551 key_dialog_save (GtkWidget *wid, gpointer userdata)
552 {
553 GtkTreeModel *store = get_store ();
554 GtkTreeIter iter;
555 struct key_binding *kb;
556 char *data1, *data2, *accel, *actiontext;
557 guint keyval;
558 GdkModifierType mod;
559
560 if (keybind_list)
561 {
562 g_slist_free_full (keybind_list, key_free);
563 keybind_list = NULL;
564 }
565
566 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
567 {
568 do
569 {
570 kb = g_new0 (struct key_binding, 1);
571
572 gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ACCEL_COLUMN, &accel,
573 ACTION_COLUMN, &actiontext,
574 D1_COLUMN, &data1,
575 D2_COLUMN, &data2,
576 -1);
577 kb->data1 = data1;
578 kb->data2 = data2;
579
580 if (accel)
581 {
582 gtk_accelerator_parse (accel, &keyval, &mod);
583
584 kb->keyval = keyval;
585 kb->mod = key_modifier_get_valid (mod);
586
587 g_free (accel);
588 }
589
590 if (actiontext)
591 {
592 kb->action = key_get_action_from_string (actiontext);
593 g_free (actiontext);
594 }
595
596 if (!accel || !actiontext)
597 key_free (kb);
598 else
599 keybind_list = g_slist_append (keybind_list, kb);
600
601 }
602 while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
603 }
604
605 if (key_save_kbs () == 0)
606 key_dialog_close (wid, NULL);
607 }
608
609 static void
key_dialog_add(GtkWidget * wid,gpointer userdata)610 key_dialog_add (GtkWidget *wid, gpointer userdata)
611 {
612 GtkTreeView *view = g_object_get_data (G_OBJECT (key_dialog), "view");
613 GtkTreeViewColumn *col;
614 GtkListStore *store = GTK_LIST_STORE (get_store ());
615 GtkTreeIter iter;
616 GtkTreePath *path;
617
618 gtk_list_store_append (store, &iter);
619
620 /* make sure the new row is visible and selected */
621 path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
622 col = gtk_tree_view_get_column (view, ACTION_COLUMN);
623 gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0.0, 0.0);
624 gtk_tree_view_set_cursor (view, path, col, TRUE);
625 gtk_tree_path_free (path);
626 }
627
628 static void
key_dialog_delete(GtkWidget * wid,gpointer userdata)629 key_dialog_delete (GtkWidget *wid, gpointer userdata)
630 {
631 GtkTreeView *view = g_object_get_data (G_OBJECT (key_dialog), "view");
632 GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
633 GtkTreeIter iter;
634 GtkTreePath *path;
635
636 if (gtkutil_treeview_get_selected (view, &iter, -1))
637 {
638 /* delete this row, select next one */
639 if (gtk_list_store_remove (store, &iter))
640 {
641 path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
642 gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 1.0, 0.0);
643 gtk_tree_view_set_cursor (view, path, NULL, FALSE);
644 gtk_tree_path_free (path);
645 }
646 }
647 }
648
649 static GtkWidget *
key_dialog_treeview_new(GtkWidget * box)650 key_dialog_treeview_new (GtkWidget *box)
651 {
652 GtkWidget *scroll;
653 GtkListStore *store, *combostore;
654 GtkTreeViewColumn *col;
655 GtkWidget *view;
656 GtkCellRenderer *render;
657 int i;
658
659 scroll = gtk_scrolled_window_new (NULL, NULL);
660 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
661 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_IN);
662
663 store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
664 G_TYPE_STRING, G_TYPE_STRING);
665 g_return_val_if_fail (store != NULL, NULL);
666
667 view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
668 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view), TRUE);
669 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (view), FALSE);
670 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view), TRUE);
671
672 g_signal_connect (G_OBJECT (view), "key-press-event",
673 G_CALLBACK (key_dialog_keypress), NULL);
674 g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW(view))),
675 "changed", G_CALLBACK (key_dialog_selection_changed), NULL);
676
677 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
678
679 render = gtk_cell_renderer_accel_new ();
680 g_object_set (render, "editable", TRUE,
681 #ifndef WIN32
682 "accel-mode", GTK_CELL_RENDERER_ACCEL_MODE_OTHER,
683 #endif
684 NULL);
685 g_signal_connect (G_OBJECT (render), "accel-edited",
686 G_CALLBACK (key_dialog_set_key), NULL);
687 gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), KEY_COLUMN,
688 "Key", render,
689 "text", KEY_COLUMN,
690 NULL);
691
692 render = gtk_cell_renderer_text_new ();
693 gtk_tree_view_insert_column_with_attributes (
694 GTK_TREE_VIEW (view), ACCEL_COLUMN,
695 "Accel", render,
696 "text", ACCEL_COLUMN,
697 NULL);
698
699 combostore = gtk_list_store_new (1, G_TYPE_STRING);
700 for (i = 0; i <= KEY_MAX_ACTIONS; i++)
701 {
702 GtkTreeIter iter;
703
704 if (key_actions[i].name[0])
705 {
706 gtk_list_store_append (combostore, &iter);
707 gtk_list_store_set (combostore, &iter, 0, key_actions[i].name, -1);
708 }
709 }
710
711 render = gtk_cell_renderer_combo_new ();
712 g_object_set (G_OBJECT (render), "model", combostore,
713 "has-entry", FALSE,
714 "editable", TRUE,
715 "text-column", 0,
716 NULL);
717 g_signal_connect (G_OBJECT (render), "edited",
718 G_CALLBACK (key_dialog_entry_edited), GINT_TO_POINTER (ACTION_COLUMN));
719 g_signal_connect (G_OBJECT (render), "changed",
720 G_CALLBACK (key_dialog_combo_changed), combostore);
721 gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), ACTION_COLUMN,
722 "Action", render,
723 "text", ACTION_COLUMN,
724 NULL);
725
726 render = gtk_cell_renderer_text_new ();
727 g_object_set (render, "editable", TRUE, NULL);
728 g_signal_connect (G_OBJECT (render), "edited",
729 G_CALLBACK (key_dialog_entry_edited), GINT_TO_POINTER (D1_COLUMN));
730 gtk_tree_view_insert_column_with_attributes (
731 GTK_TREE_VIEW (view), D1_COLUMN,
732 "Data1", render,
733 "text", D1_COLUMN,
734 NULL);
735
736 render = gtk_cell_renderer_text_new ();
737 g_object_set (render, "editable", TRUE, NULL);
738 g_signal_connect (G_OBJECT (render), "edited",
739 G_CALLBACK (key_dialog_entry_edited), GINT_TO_POINTER (D2_COLUMN));
740 gtk_tree_view_insert_column_with_attributes (
741 GTK_TREE_VIEW (view), D2_COLUMN,
742 "Data2", render,
743 "text", D2_COLUMN,
744 NULL);
745
746 col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), KEY_COLUMN);
747 gtk_tree_view_column_set_fixed_width (col, 200);
748 gtk_tree_view_column_set_resizable (col, TRUE);
749 col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), ACCEL_COLUMN);
750 gtk_tree_view_column_set_visible (col, FALSE);
751 col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), ACTION_COLUMN);
752 gtk_tree_view_column_set_fixed_width (col, 160);
753 col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), D1_COLUMN);
754 gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
755 gtk_tree_view_column_set_min_width (col, 80);
756 gtk_tree_view_column_set_resizable (col, TRUE);
757 col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), D2_COLUMN);
758 gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
759 gtk_tree_view_column_set_min_width (col, 80);
760 gtk_tree_view_column_set_resizable (col, TRUE);
761
762 gtk_container_add (GTK_CONTAINER (scroll), view);
763 gtk_container_add (GTK_CONTAINER (box), scroll);
764
765 return view;
766 }
767
768 static void
key_dialog_load(GtkListStore * store)769 key_dialog_load (GtkListStore *store)
770 {
771 struct key_binding *kb = NULL;
772 char *label_text, *accel_text;
773 GtkTreeIter iter;
774 GSList *list = keybind_list;
775
776 while (list)
777 {
778 kb = (struct key_binding*)list->data;
779
780 label_text = gtk_accelerator_get_label (kb->keyval, kb->mod);
781 accel_text = gtk_accelerator_name (kb->keyval, kb->mod);
782
783 gtk_list_store_append (store, &iter);
784 gtk_list_store_set (store, &iter,
785 KEY_COLUMN, label_text,
786 ACCEL_COLUMN, accel_text,
787 ACTION_COLUMN, key_actions[kb->action].name,
788 D1_COLUMN, kb->data1,
789 D2_COLUMN, kb->data2, -1);
790
791 g_free (accel_text);
792 g_free (label_text);
793
794 list = g_slist_next (list);
795 }
796 }
797
798 void
key_dialog_show()799 key_dialog_show ()
800 {
801 GtkWidget *vbox, *box;
802 GtkWidget *view, *xtext;
803 GtkListStore *store;
804 char buf[128];
805
806 if (key_dialog)
807 {
808 mg_bring_tofront (key_dialog);
809 return;
810 }
811
812 g_snprintf(buf, sizeof(buf), _("Keyboard Shortcuts - %s"), _(DISPLAY_NAME));
813 key_dialog = mg_create_generic_tab ("editkeys", buf, TRUE, FALSE, key_dialog_close,
814 NULL, 600, 360, &vbox, 0);
815
816 view = key_dialog_treeview_new (vbox);
817 xtext = gtk_xtext_new (colors, 0);
818 gtk_box_pack_start (GTK_BOX (vbox), xtext, FALSE, TRUE, 2);
819 gtk_xtext_set_font (GTK_XTEXT (xtext), prefs.hex_text_font);
820
821 g_object_set_data (G_OBJECT (key_dialog), "view", view);
822 g_object_set_data (G_OBJECT (key_dialog), "xtext", xtext);
823
824 box = gtk_hbutton_box_new ();
825 gtk_button_box_set_layout (GTK_BUTTON_BOX (box), GTK_BUTTONBOX_SPREAD);
826 gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, FALSE, 2);
827 gtk_container_set_border_width (GTK_CONTAINER (box), 5);
828
829 gtkutil_button (box, GTK_STOCK_NEW, NULL, key_dialog_add,
830 NULL, _("Add"));
831 gtkutil_button (box, GTK_STOCK_DELETE, NULL, key_dialog_delete,
832 NULL, _("Delete"));
833 gtkutil_button (box, GTK_STOCK_CANCEL, NULL, key_dialog_close,
834 NULL, _("Cancel"));
835 gtkutil_button (box, GTK_STOCK_SAVE, NULL, key_dialog_save,
836 NULL, _("Save"));
837
838 store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view)));
839 key_dialog_load (store);
840
841 gtk_widget_show_all (key_dialog);
842 }
843
844 static int
key_save_kbs(void)845 key_save_kbs (void)
846 {
847 int fd;
848 char buf[512];
849 char *accel_text;
850 GSList *list = keybind_list;
851 struct key_binding *kb;
852
853 fd = hexchat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY,
854 0x180, XOF_DOMODE);
855 if (fd < 0)
856 return 1;
857 write (fd, buf, g_snprintf (buf, 510, "# HexChat key bindings config file\n\n"));
858
859 while (list)
860 {
861 kb = list->data;
862
863 accel_text = gtk_accelerator_name (kb->keyval, kb->mod);
864
865 g_snprintf (buf, 510, "ACCEL=%s\n%s\n", accel_text, key_actions[kb->action].name);
866 write (fd, buf, strlen (buf));
867 g_free (accel_text);
868
869 if (kb->data1 && kb->data1[0])
870 write (fd, buf, g_snprintf (buf, 510, "D1:%s\n", kb->data1));
871 else
872 write (fd, "D1!\n", 4);
873
874 if (kb->data2 && kb->data2[0])
875 write (fd, buf, g_snprintf (buf, 510, "D2:%s\n", kb->data2));
876 else
877 write (fd, "D2!\n", 4);
878
879 write (fd, "\n", 1);
880
881 list = g_slist_next (list);
882 }
883
884 close (fd);
885 return 0;
886 }
887
888 #define KBSTATE_MOD 0
889 #define KBSTATE_KEY 1
890 #define KBSTATE_ACT 2
891 #define KBSTATE_DT1 3
892 #define KBSTATE_DT2 4
893
894 #define STRIP_WHITESPACE \
895 while (buf[0] == ' ' || buf[0] == '\t') \
896 buf++; \
897 len = strlen (buf); \
898 while (buf[len] == ' ' || buf[len] == '\t') \
899 { \
900 buf[len] = 0; \
901 len--; \
902 } \
903
904 static inline int
key_load_kbs_helper_mod(char * buf,GdkModifierType * out)905 key_load_kbs_helper_mod (char *buf, GdkModifierType *out)
906 {
907 int n, len, mod = 0;
908
909 /* First strip off the fluff */
910 STRIP_WHITESPACE
911
912 if (strcmp (buf, "None") == 0)
913 {
914 *out = 0;
915 return 0;
916 }
917 for (n = 0; n < len; n++)
918 {
919 switch (buf[n])
920 {
921 case 'C':
922 mod |= GDK_CONTROL_MASK;
923 break;
924 case 'A':
925 mod |= GDK_MOD1_MASK;
926 break;
927 case 'S':
928 mod |= GDK_SHIFT_MASK;
929 break;
930 default:
931 return 1;
932 }
933 }
934
935 *out = mod;
936 return 0;
937 }
938
939 static int
key_load_kbs(void)940 key_load_kbs (void)
941 {
942 char *buf, *ibuf;
943 struct stat st;
944 struct key_binding *kb = NULL;
945 int fd, len, state = 0, pnt = 0;
946 guint keyval;
947 GdkModifierType mod = 0;
948 off_t size;
949
950 fd = hexchat_open_file ("keybindings.conf", O_RDONLY, 0, 0);
951 if (fd < 0)
952 {
953 ibuf = g_strdup (default_kb_cfg);
954 size = strlen (default_kb_cfg);
955 }
956 else
957 {
958 if (fstat (fd, &st) != 0)
959 {
960 close (fd);
961 return 1;
962 }
963
964 ibuf = g_malloc(st.st_size);
965 read (fd, ibuf, st.st_size);
966 size = st.st_size;
967 close (fd);
968 }
969
970 if (keybind_list)
971 {
972 g_slist_free_full (keybind_list, key_free);
973 keybind_list = NULL;
974 }
975
976 while (buf_get_line (ibuf, &buf, &pnt, size))
977 {
978 if (buf[0] == '#')
979 continue;
980 if (strlen (buf) == 0)
981 continue;
982
983 switch (state)
984 {
985 case KBSTATE_MOD:
986 kb = g_new0 (struct key_binding, 1);
987
988 /* New format */
989 if (strncmp (buf, "ACCEL=", 6) == 0)
990 {
991 buf += 6;
992
993 gtk_accelerator_parse (buf, &keyval, &mod);
994
995
996 kb->keyval = keyval;
997 kb->mod = key_modifier_get_valid (mod);
998
999 state = KBSTATE_ACT;
1000 continue;
1001 }
1002
1003 if (key_load_kbs_helper_mod (buf, &mod))
1004 goto corrupt_file;
1005
1006 kb->mod = mod;
1007
1008 state = KBSTATE_KEY;
1009 continue;
1010
1011 case KBSTATE_KEY:
1012 STRIP_WHITESPACE
1013
1014 keyval = gdk_keyval_from_name (buf);
1015 if (keyval == 0)
1016 {
1017 g_free (ibuf);
1018 return 2;
1019 }
1020
1021 kb->keyval = keyval;
1022
1023 state = KBSTATE_ACT;
1024 continue;
1025
1026 case KBSTATE_ACT:
1027 STRIP_WHITESPACE
1028
1029 kb->action = key_get_action_from_string (buf);
1030
1031 if (kb->action == KEY_MAX_ACTIONS + 1)
1032 {
1033 g_free (ibuf);
1034 return 3;
1035 }
1036
1037 state = KBSTATE_DT1;
1038 continue;
1039
1040 case KBSTATE_DT1:
1041 case KBSTATE_DT2:
1042 if (state == KBSTATE_DT1)
1043 kb->data1 = kb->data2 = NULL;
1044
1045 while (buf[0] == ' ' || buf[0] == '\t')
1046 buf++;
1047
1048 if (buf[0] != 'D')
1049 {
1050 g_free (ibuf);
1051 return 4;
1052 }
1053
1054 switch (buf[1])
1055 {
1056 case '1':
1057 if (state != KBSTATE_DT1)
1058 goto corrupt_file;
1059 break;
1060 case '2':
1061 if (state != KBSTATE_DT2)
1062 goto corrupt_file;
1063 break;
1064 default:
1065 goto corrupt_file;
1066 }
1067
1068 if (buf[2] == ':')
1069 {
1070 len = strlen (buf);
1071 /* Add one for the NULL, subtract 3 for the "Dx:" */
1072 len++;
1073 len -= 3;
1074 if (state == KBSTATE_DT1)
1075 {
1076 kb->data1 = g_strndup (&buf[3], len);
1077 } else
1078 {
1079 kb->data2 = g_strndup (&buf[3], len);
1080 }
1081 } else if (buf[2] == '!')
1082 {
1083 if (state == KBSTATE_DT1)
1084 kb->data1 = NULL;
1085 else
1086 kb->data2 = NULL;
1087 }
1088 if (state == KBSTATE_DT1)
1089 {
1090 state = KBSTATE_DT2;
1091 continue;
1092 } else
1093 {
1094 keybind_list = g_slist_append (keybind_list, kb);
1095
1096 state = KBSTATE_MOD;
1097 }
1098
1099 continue;
1100 }
1101 }
1102 g_free (ibuf);
1103 return 0;
1104
1105 corrupt_file:
1106 g_free (ibuf);
1107 g_free (kb);
1108 return 5;
1109 }
1110
1111 /* ***** Key actions start here *********** */
1112
1113 /* See the NOTES above --AGL */
1114
1115 /* "Run command" */
1116 static int
key_action_handle_command(GtkWidget * wid,GdkEventKey * evt,char * d1,char * d2,struct session * sess)1117 key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1,
1118 char *d2, struct session *sess)
1119 {
1120 int ii, oi, len;
1121 char out[2048], d = 0;
1122
1123 if (!d1)
1124 return 0;
1125
1126 len = strlen (d1);
1127
1128 /* Replace each "\n" substring with '\n' */
1129 for (ii = oi = 0; ii < len; ii++)
1130 {
1131 d = d1[ii];
1132 if (d == '\\')
1133 {
1134 ii++;
1135 d = d1[ii];
1136 if (d == 'n')
1137 out[oi++] = '\n';
1138 else if (d == '\\')
1139 out[oi++] = '\\';
1140 else
1141 {
1142 out[oi++] = '\\';
1143 out[oi++] = d;
1144 }
1145 continue;
1146 }
1147 out[oi++] = d;
1148 }
1149 out[oi] = 0;
1150
1151 handle_multiline (sess, out, 0, 0);
1152 return 0;
1153 }
1154
1155 /*
1156 * Check if the given session is inside the main window. This predicate
1157 * is passed to lastact_getfirst() as a way to filter out detached sessions.
1158 * XXX: Consider moving this in a different file?
1159 */
1160 static int
session_check_is_tab(session * sess)1161 session_check_is_tab(session *sess)
1162 {
1163 if (!sess || !sess->gui)
1164 return FALSE;
1165
1166 return (sess->gui->is_tab);
1167 }
1168
1169 static int
key_action_page_switch(GtkWidget * wid,GdkEventKey * evt,char * d1,char * d2,struct session * sess)1170 key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1,
1171 char *d2, struct session *sess)
1172 {
1173 session *newsess;
1174 int len, i, num;
1175
1176 if (!d1)
1177 return 1;
1178
1179 len = strlen (d1);
1180 if (!len)
1181 return 1;
1182
1183 if (strcasecmp(d1, "auto") == 0)
1184 {
1185 /* Auto switch makes no sense in detached sessions */
1186 if (!sess->gui->is_tab)
1187 return 1;
1188
1189 /* Obtain a session with recent activity */
1190 newsess = lastact_getfirst(session_check_is_tab);
1191
1192 if (newsess)
1193 {
1194 /*
1195 * Only sessions in the current window should be considered (i.e.
1196 * we don't want to move the focus on a different window). This
1197 * call could, in theory, do this, but we checked before that
1198 * newsess->gui->is_tab and sess->gui->is_tab.
1199 */
1200 mg_bring_tofront_sess(newsess);
1201 return 0;
1202 }
1203 else
1204 return 1;
1205 }
1206
1207 for (i = 0; i < len; i++)
1208 {
1209 if (d1[i] < '0' || d1[i] > '9')
1210 {
1211 if (i == 0 && (d1[i] == '+' || d1[i] == '-'))
1212 continue;
1213 else
1214 return 1;
1215 }
1216 }
1217
1218 num = atoi (d1);
1219 if (!d2)
1220 num--;
1221 if (!d2 || d2[0] == 0)
1222 mg_switch_page (FALSE, num);
1223 else
1224 mg_switch_page (TRUE, num);
1225 return 0;
1226 }
1227
1228 int
key_action_insert(GtkWidget * wid,GdkEventKey * evt,char * d1,char * d2,struct session * sess)1229 key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
1230 struct session *sess)
1231 {
1232 int tmp_pos;
1233
1234 if (!d1)
1235 return 1;
1236
1237 tmp_pos = SPELL_ENTRY_GET_POS (wid);
1238 SPELL_ENTRY_INSERT (wid, d1, strlen (d1), &tmp_pos);
1239 SPELL_ENTRY_SET_POS (wid, tmp_pos);
1240 return 2;
1241 }
1242
1243 /* handles PageUp/Down keys */
1244 static int
key_action_scroll_page(GtkWidget * wid,GdkEventKey * evt,char * d1,char * d2,struct session * sess)1245 key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt, char *d1,
1246 char *d2, struct session *sess)
1247 {
1248 int value, end;
1249 GtkAdjustment *adj;
1250 enum scroll_type { PAGE_TOP, PAGE_BOTTOM, PAGE_UP, PAGE_DOWN, LINE_UP, LINE_DOWN };
1251 int type = PAGE_DOWN;
1252
1253 if (d1)
1254 {
1255 if (!g_ascii_strcasecmp (d1, "top"))
1256 type = PAGE_TOP;
1257 else if (!g_ascii_strcasecmp (d1, "bottom"))
1258 type = PAGE_BOTTOM;
1259 else if (!g_ascii_strcasecmp (d1, "up"))
1260 type = PAGE_UP;
1261 else if (!g_ascii_strcasecmp (d1, "down"))
1262 type = PAGE_DOWN;
1263 else if (!g_ascii_strcasecmp (d1, "+1"))
1264 type = LINE_DOWN;
1265 else if (!g_ascii_strcasecmp (d1, "-1"))
1266 type = LINE_UP;
1267 }
1268
1269 if (!sess)
1270 return 0;
1271
1272 adj = gtk_range_get_adjustment (GTK_RANGE (sess->gui->vscrollbar));
1273 end = gtk_adjustment_get_upper (adj) - gtk_adjustment_get_lower (adj) - gtk_adjustment_get_page_size (adj);
1274
1275 switch (type)
1276 {
1277 case PAGE_TOP:
1278 value = 0;
1279 break;
1280
1281 case PAGE_BOTTOM:
1282 value = end;
1283 break;
1284
1285 case PAGE_UP:
1286 value = gtk_adjustment_get_value (adj) - (gtk_adjustment_get_page_size (adj) - 1);
1287 break;
1288
1289 case PAGE_DOWN:
1290 value = gtk_adjustment_get_value (adj) + (gtk_adjustment_get_page_size (adj) - 1);
1291 break;
1292
1293 case LINE_UP:
1294 value = gtk_adjustment_get_value (adj) - 1.0;
1295 break;
1296
1297 case LINE_DOWN:
1298 value = gtk_adjustment_get_value (adj) + 1.0;
1299 break;
1300 }
1301
1302 if (value < 0)
1303 value = 0;
1304 if (value > end)
1305 value = end;
1306
1307 gtk_adjustment_set_value (adj, value);
1308
1309 return 0;
1310 }
1311
1312 static int
key_action_set_buffer(GtkWidget * wid,GdkEventKey * evt,char * d1,char * d2,struct session * sess)1313 key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
1314 struct session *sess)
1315 {
1316 if (!d1)
1317 return 1;
1318 if (d1[0] == 0)
1319 return 1;
1320
1321 SPELL_ENTRY_SET_TEXT (wid, d1);
1322 SPELL_ENTRY_SET_POS (wid, -1);
1323
1324 return 2;
1325 }
1326
1327 static int
key_action_history_up(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1328 key_action_history_up (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
1329 struct session *sess)
1330 {
1331 char *new_line;
1332
1333 new_line = history_up (&sess->history, SPELL_ENTRY_GET_TEXT (wid));
1334 if (new_line)
1335 {
1336 SPELL_ENTRY_SET_TEXT (wid, new_line);
1337 SPELL_ENTRY_SET_POS (wid, -1);
1338 }
1339
1340 return 2;
1341 }
1342
1343 static int
key_action_history_down(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1344 key_action_history_down (GtkWidget * wid, GdkEventKey * ent, char *d1,
1345 char *d2, struct session *sess)
1346 {
1347 char *new_line;
1348
1349 new_line = history_down (&sess->history);
1350 if (new_line)
1351 {
1352 SPELL_ENTRY_SET_TEXT (wid, new_line);
1353 SPELL_ENTRY_SET_POS (wid, -1);
1354 }
1355
1356 return 2;
1357 }
1358
1359 /* old data that we reuse */
1360 static struct gcomp_data old_gcomp;
1361
1362 /* work on the data, ie return only channels */
1363 static int
double_chan_cb(session * lsess,GList ** list)1364 double_chan_cb (session *lsess, GList **list)
1365 {
1366 if (lsess->type == SESS_CHANNEL)
1367 *list = g_list_prepend(*list, lsess->channel);
1368 return TRUE;
1369 }
1370
1371 /* convert a slist -> list. */
1372 static GList *
chanlist_double_list(GSList * inlist)1373 chanlist_double_list (GSList *inlist)
1374 {
1375 GList *list = NULL;
1376 g_slist_foreach(inlist, (GFunc)double_chan_cb, &list);
1377 return list;
1378 }
1379
1380 /* handle commands */
1381 static int
double_cmd_cb(struct popup * pop,GList ** list)1382 double_cmd_cb (struct popup *pop, GList **list)
1383 {
1384 *list = g_list_prepend(*list, pop->name);
1385 return TRUE;
1386 }
1387
1388 /* convert a slist -> list. */
1389 static GList *
cmdlist_double_list(GSList * inlist)1390 cmdlist_double_list (GSList *inlist)
1391 {
1392 GList *list = NULL;
1393 g_slist_foreach (inlist, (GFunc)double_cmd_cb, &list);
1394 return list;
1395 }
1396
1397 static char *
gcomp_nick_func(char * data)1398 gcomp_nick_func (char *data)
1399 {
1400 if (data)
1401 return ((struct User *)data)->nick;
1402 return "";
1403 }
1404
1405 void
key_action_tab_clean(void)1406 key_action_tab_clean(void)
1407 {
1408 if (old_gcomp.elen)
1409 {
1410 old_gcomp.data[0] = 0;
1411 old_gcomp.elen = 0;
1412 }
1413 }
1414
1415 /* For use in sorting the user list for completion
1416
1417 This sorts everyone by the last talked time except your own nick
1418 which is forced to the bottom of the list to avoid completing your
1419 own name, which is very unlikely.
1420 */
1421 static int
talked_recent_cmp(struct User * a,struct User * b)1422 talked_recent_cmp (struct User *a, struct User *b)
1423 {
1424 if (a->me)
1425 return -1;
1426
1427 if (b->me)
1428 return 1;
1429
1430 if (a->lasttalk < b->lasttalk)
1431 return -1;
1432
1433 if (a->lasttalk > b->lasttalk)
1434 return 1;
1435
1436 return 0;
1437 }
1438
1439 #define COMP_BUF 2048
1440
1441 static inline glong
len_to_offset(const char * str,glong len)1442 len_to_offset (const char *str, glong len)
1443 {
1444 return g_utf8_pointer_to_offset (str, str + len);
1445 }
1446
1447 static inline glong
offset_to_len(const char * str,glong offset)1448 offset_to_len (const char *str, glong offset)
1449 {
1450 return g_utf8_offset_to_pointer (str, offset) - str;
1451 }
1452
1453 static int
key_action_tab_comp(GtkWidget * t,GdkEventKey * entry,char * d1,char * d2,struct session * sess)1454 key_action_tab_comp (GtkWidget *t, GdkEventKey *entry, char *d1, char *d2,
1455 struct session *sess)
1456 {
1457 int len = 0, elen = 0, i = 0, cursor_pos, ent_start = 0, comp = 0, prefix_len, skip_len = 0;
1458 gboolean is_nick = FALSE, is_cmd = FALSE, found = FALSE, has_nick_prefix = FALSE;
1459 char ent[CHANLEN], *postfix = NULL, *result, *ch;
1460 GList *list = NULL, *tmp_list = NULL;
1461 const char *text;
1462 GCompletion *gcomp = NULL;
1463 GString *buf;
1464
1465 /* force the IM Context to reset */
1466 SPELL_ENTRY_SET_EDITABLE (t, FALSE);
1467 SPELL_ENTRY_SET_EDITABLE (t, TRUE);
1468
1469 text = SPELL_ENTRY_GET_TEXT (t);
1470 if (text[0] == 0)
1471 return 1;
1472
1473 len = g_utf8_strlen (text, -1); /* must be null terminated */
1474
1475 cursor_pos = SPELL_ENTRY_GET_POS (t);
1476
1477 /* handle "nick: " or "nick " or "#channel "*/
1478 ch = g_utf8_find_prev_char(text, g_utf8_offset_to_pointer(text,cursor_pos));
1479 if (ch && ch[0] == ' ')
1480 {
1481 skip_len++;
1482 ch = g_utf8_find_prev_char(text, ch);
1483 if (!ch)
1484 return 2;
1485
1486 cursor_pos = g_utf8_pointer_to_offset(text, ch);
1487 if (cursor_pos && (g_utf8_get_char_validated(ch, -1) == ':' ||
1488 g_utf8_get_char_validated(ch, -1) == ',' ||
1489 g_utf8_get_char_validated (ch, -1) == g_utf8_get_char_validated (prefs.hex_completion_suffix, -1)))
1490 {
1491 skip_len++;
1492 }
1493 else
1494 cursor_pos = g_utf8_pointer_to_offset(text, g_utf8_offset_to_pointer(ch, 1));
1495 }
1496
1497 comp = skip_len;
1498
1499 /* store the text following the cursor for reinsertion later */
1500 if ((cursor_pos + skip_len) < len)
1501 postfix = g_utf8_offset_to_pointer(text, cursor_pos + skip_len);
1502
1503 for (ent_start = cursor_pos; ; --ent_start)
1504 {
1505 if (ent_start == 0)
1506 break;
1507 ch = g_utf8_offset_to_pointer(text, ent_start - 1);
1508 if (ch && ch[0] == ' ')
1509 break;
1510 }
1511
1512 if (ent_start == 0 && text[0] == prefs.hex_input_command_char[0])
1513 {
1514 ent_start++;
1515 is_cmd = TRUE;
1516 }
1517 else if (strchr (sess->server->chantypes, text[ent_start]) == NULL)
1518 {
1519 is_nick = TRUE;
1520 if (strchr (sess->server->nick_prefixes, text[ent_start]) != NULL)
1521 {
1522 if (ent_start == 0)
1523 has_nick_prefix = TRUE;
1524 ent_start++;
1525 }
1526 }
1527
1528 prefix_len = ent_start;
1529 elen = cursor_pos - ent_start;
1530
1531 g_utf8_strncpy (ent, g_utf8_offset_to_pointer (text, prefix_len), elen);
1532
1533 if (sess->type == SESS_DIALOG && is_nick)
1534 {
1535 /* tab in a dialog completes the other person's name */
1536 if (rfc_ncasecmp (sess->channel, ent, elen) == 0)
1537 {
1538 result = sess->channel;
1539 is_nick = FALSE;
1540 }
1541 else
1542 return 2;
1543 }
1544 else
1545 {
1546 if (is_nick)
1547 {
1548 gcomp = g_completion_new((GCompletionFunc)gcomp_nick_func);
1549 tmp_list = userlist_double_list(sess); /* create a temp list so we can free the memory */
1550 if (prefs.hex_completion_sort == 1) /* sort in last-talk order? */
1551 tmp_list = g_list_sort (tmp_list, (void *)talked_recent_cmp);
1552 }
1553 else
1554 {
1555 gcomp = g_completion_new (NULL);
1556 if (is_cmd)
1557 {
1558 tmp_list = cmdlist_double_list (command_list);
1559 for(i = 0; xc_cmds[i].name != NULL ; i++)
1560 {
1561 tmp_list = g_list_prepend (tmp_list, xc_cmds[i].name);
1562 }
1563 tmp_list = plugin_command_list(tmp_list);
1564 }
1565 else
1566 tmp_list = chanlist_double_list (sess_list);
1567 }
1568 tmp_list = g_list_reverse(tmp_list); /* make the comp entries turn up in the right order */
1569 g_completion_set_compare (gcomp, (GCompletionStrncmpFunc)rfc_ncasecmp);
1570 if (tmp_list)
1571 {
1572 g_completion_add_items (gcomp, tmp_list);
1573 g_list_free (tmp_list);
1574 }
1575
1576 if (comp && !(rfc_ncasecmp(old_gcomp.data, ent, old_gcomp.elen) == 0))
1577 {
1578 key_action_tab_clean ();
1579 comp = 0;
1580 }
1581
1582 list = g_completion_complete_utf8 (gcomp, comp ? old_gcomp.data : ent, &result);
1583
1584 if (result == NULL) /* No matches found */
1585 {
1586 g_completion_free(gcomp);
1587 return 2;
1588 }
1589
1590 if (comp) /* existing completion */
1591 {
1592 while(list) /* find the current entry */
1593 {
1594 if(rfc_ncasecmp(list->data, ent, elen) == 0)
1595 {
1596 found = TRUE;
1597 break;
1598 }
1599 list = list->next;
1600 }
1601
1602 if (found)
1603 {
1604 if (!(d1 && d1[0])) /* not holding down shift */
1605 {
1606 if (g_list_next(list) == NULL)
1607 list = g_list_first(list);
1608 else
1609 list = g_list_next(list);
1610 }
1611 else
1612 {
1613 if (g_list_previous(list) == NULL)
1614 list = g_list_last(list);
1615 else
1616 list = g_list_previous(list);
1617 }
1618 g_free(result);
1619 result = (char*)list->data;
1620 }
1621 else
1622 {
1623 g_free(result);
1624 g_completion_free(gcomp);
1625 return 2;
1626 }
1627 }
1628 else
1629 {
1630 strcpy(old_gcomp.data, ent);
1631 old_gcomp.elen = elen;
1632
1633 /* Get the first nick and put out the data for future nickcompletes */
1634 if (prefs.hex_completion_amount > 0 && g_list_length (list) <= (guint) prefs.hex_completion_amount)
1635 {
1636 g_free(result);
1637 result = (char*)list->data;
1638 }
1639 else
1640 {
1641 /* bash style completion */
1642 if (g_list_next(list) != NULL)
1643 {
1644 buf = g_string_sized_new (MAX(COMP_BUF, len + NICKLEN));
1645 if (strlen (result) > elen) /* the largest common prefix is larger than nick, change the data */
1646 {
1647 if (prefix_len)
1648 g_string_append_len (buf, text, offset_to_len (text, prefix_len));
1649 g_string_append (buf, result);
1650 cursor_pos = buf->len;
1651 g_free(result);
1652 if (postfix)
1653 {
1654 g_string_append_c (buf, ' ');
1655 g_string_append (buf, postfix);
1656 }
1657 SPELL_ENTRY_SET_TEXT (t, buf->str);
1658 SPELL_ENTRY_SET_POS (t, len_to_offset (buf->str, cursor_pos));
1659 g_string_erase (buf, 0, -1);
1660 }
1661 else
1662 g_free(result);
1663
1664 while (list)
1665 {
1666 len = buf->len;
1667 elen = strlen (list->data); /* next item to add */
1668 if (len + elen + 2 >= COMP_BUF) /* +2 is space + null */
1669 {
1670 PrintText (sess, buf->str);
1671 g_string_erase (buf, 0, -1);
1672 }
1673 g_string_append (buf, (char*)list->data);
1674 g_string_append_c (buf, ' ');
1675 list = list->next;
1676 }
1677 PrintText (sess, buf->str);
1678 g_completion_free(gcomp);
1679 g_string_free (buf, TRUE);
1680 return 2;
1681 }
1682 /* Only one matching entry */
1683 g_free(result);
1684 result = list->data;
1685 }
1686 }
1687 }
1688
1689 if(result)
1690 {
1691 buf = g_string_sized_new (len + NICKLEN);
1692 if (prefix_len)
1693 g_string_append_len (buf, text, offset_to_len (text, prefix_len));
1694 g_string_append (buf, result);
1695 if((!prefix_len || has_nick_prefix) && is_nick && prefs.hex_completion_suffix[0] != '\0')
1696 g_string_append_unichar (buf, g_utf8_get_char_validated (prefs.hex_completion_suffix, -1));
1697 g_string_append_c (buf, ' ');
1698 cursor_pos = buf->len;
1699 if (postfix)
1700 g_string_append (buf, postfix);
1701 SPELL_ENTRY_SET_TEXT (t, buf->str);
1702 SPELL_ENTRY_SET_POS (t, len_to_offset (buf->str, cursor_pos));
1703 g_string_free (buf, TRUE);
1704 }
1705 if (gcomp)
1706 g_completion_free(gcomp);
1707 return 2;
1708 }
1709 #undef COMP_BUF
1710
1711 static int
key_action_comp_chng(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1712 key_action_comp_chng (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
1713 struct session *sess)
1714 {
1715 key_action_tab_comp(wid, ent, d1, d2, sess);
1716 return 2;
1717 }
1718
1719
1720 static int
key_action_replace(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1721 key_action_replace (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2,
1722 struct session *sess)
1723 {
1724 replace_handle (wid);
1725 return 1;
1726 }
1727
1728
1729 static int
key_action_move_tab_left(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1730 key_action_move_tab_left (GtkWidget * wid, GdkEventKey * ent, char *d1,
1731 char *d2, struct session *sess)
1732 {
1733 mg_move_tab (sess, +1);
1734 return 2; /* don't allow default action */
1735 }
1736
1737 static int
key_action_move_tab_right(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1738 key_action_move_tab_right (GtkWidget * wid, GdkEventKey * ent, char *d1,
1739 char *d2, struct session *sess)
1740 {
1741 mg_move_tab (sess, -1);
1742 return 2; /* -''- */
1743 }
1744
1745 static int
key_action_move_tab_family_left(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1746 key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * ent, char *d1,
1747 char *d2, struct session *sess)
1748 {
1749 mg_move_tab_family (sess, +1);
1750 return 2; /* don't allow default action */
1751 }
1752
1753 static int
key_action_move_tab_family_right(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1754 key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * ent, char *d1,
1755 char *d2, struct session *sess)
1756 {
1757 mg_move_tab_family (sess, -1);
1758 return 2; /* -''- */
1759 }
1760
1761 static int
key_action_put_history(GtkWidget * wid,GdkEventKey * ent,char * d1,char * d2,struct session * sess)1762 key_action_put_history (GtkWidget * wid, GdkEventKey * ent, char *d1,
1763 char *d2, struct session *sess)
1764 {
1765 history_add (&sess->history, SPELL_ENTRY_GET_TEXT (wid));
1766 SPELL_ENTRY_SET_TEXT (wid, "");
1767 return 2; /* -''- */
1768 }
1769
1770
1771 /* -------- */
1772
1773 static void
replace_handle(GtkWidget * t)1774 replace_handle (GtkWidget *t)
1775 {
1776 const char *text, *postfix_pnt;
1777 struct popup *pop;
1778 GSList *list = replace_list;
1779 char word[256];
1780 char postfix[256];
1781 char outbuf[4096];
1782 int c, len, xlen;
1783
1784 text = SPELL_ENTRY_GET_TEXT (t);
1785
1786 len = strlen (text);
1787 if (len < 1)
1788 return;
1789
1790 for (c = len - 1; c > 0; c--)
1791 {
1792 if (text[c] == ' ')
1793 break;
1794 }
1795 if (text[c] == ' ')
1796 c++;
1797 xlen = c;
1798 if (len - c >= (sizeof (word) - 12))
1799 return;
1800 if (len - c < 1)
1801 return;
1802 memcpy (word, &text[c], len - c);
1803 word[len - c] = 0;
1804 len = strlen (word);
1805 if (word[0] == '\'' && word[len] == '\'')
1806 return;
1807 postfix_pnt = NULL;
1808 for (c = 0; c < len; c++)
1809 {
1810 if (word[c] == '\'')
1811 {
1812 postfix_pnt = &word[c + 1];
1813 word[c] = 0;
1814 break;
1815 }
1816 }
1817
1818 if (postfix_pnt != NULL)
1819 {
1820 if (strlen (postfix_pnt) > sizeof (postfix) - 12)
1821 return;
1822 strcpy (postfix, postfix_pnt);
1823 }
1824 while (list)
1825 {
1826 pop = (struct popup *) list->data;
1827 if (strcmp (pop->name, word) == 0)
1828 {
1829 memcpy (outbuf, text, xlen);
1830 outbuf[xlen] = 0;
1831 if (postfix_pnt == NULL)
1832 g_snprintf (word, sizeof (word), "%s", pop->cmd);
1833 else
1834 g_snprintf (word, sizeof (word), "%s%s", pop->cmd, postfix);
1835 g_strlcat (outbuf, word, sizeof(outbuf));
1836 SPELL_ENTRY_SET_TEXT (t, outbuf);
1837 SPELL_ENTRY_SET_POS (t, -1);
1838 return;
1839 }
1840 list = list->next;
1841 }
1842 }
1843
1844