prefs_actions.c (35617B)
1 /* 2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client 3 * Copyright (C) 1999-2024 the Claws Mail Team and Hiroyuki Yamamoto 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 #include "defs.h" 20 21 #include <glib.h> 22 #include <glib/gi18n.h> 23 #include <gtk/gtk.h> 24 #include <gdk/gdkkeysyms.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <err.h> 29 #include <errno.h> 30 31 #include "prefs_gtk.h" 32 #include "inc.h" 33 #include "utils.h" 34 #include "gtkutils.h" 35 #include "manage_window.h" 36 #include "mainwindow.h" 37 #include "prefs_common.h" 38 #include "alertpanel.h" 39 #include "prefs_actions.h" 40 #include "action.h" 41 #include "manual.h" 42 #include "menu.h" 43 #include "prefs_toolbar.h" 44 #include "file-utils.h" 45 46 enum { 47 PREFS_ACTIONS_STRING, /*!< string pointer managed by list store, 48 * and never touched or retrieved by 49 * us */ 50 PREFS_ACTIONS_DATA, /*!< pointer to string that is not managed by 51 * the list store, and which is retrieved 52 * and touched by us */ 53 PREFS_ACTIONS_VALID, /*!< contains a valid action, otherwise "(New)" */ 54 N_PREFS_ACTIONS_COLUMNS 55 }; 56 57 static struct Actions 58 { 59 GtkWidget *window; 60 61 GtkWidget *ok_btn; 62 GtkWidget *name_entry; 63 GtkWidget *cmd_entry; 64 GtkWidget *info_btn; 65 GtkWidget *shell_radiobtn; 66 GtkWidget *filter_radiobtn; 67 68 GtkWidget *actions_list_view; 69 } actions; 70 71 static int modified = FALSE; 72 static int modified_list = FALSE; 73 74 /* widget creating functions */ 75 static void prefs_actions_create (MainWindow *mainwin); 76 static void prefs_actions_set_dialog (void); 77 static gint prefs_actions_clist_set_row (gint row); 78 79 static void prefs_actions_register_cb (GtkWidget *w, 80 gpointer data); 81 static void prefs_actions_substitute_cb (GtkWidget *w, 82 gpointer data); 83 static void prefs_actions_delete_cb (gpointer gtk_action, gpointer data); 84 static void prefs_actions_delete_all_cb (gpointer gtk_action, gpointer data); 85 static void prefs_actions_clear_cb (gpointer gtk_action, gpointer data); 86 static void prefs_actions_duplicate_cb (gpointer gtk_action, gpointer data); 87 static void prefs_actions_top_cb (GtkWidget *w, gpointer data); 88 static void prefs_actions_up_cb (GtkWidget *w, gpointer data); 89 static void prefs_actions_down_cb (GtkWidget *w, gpointer data); 90 static void prefs_actions_bottom_cb (GtkWidget *w, gpointer data); 91 static gint prefs_actions_deleted (GtkWidget *widget, 92 GdkEventAny *event, 93 gpointer *data); 94 static gboolean prefs_actions_key_pressed(GtkWidget *widget, 95 GdkEventKey *event, 96 gpointer data); 97 static gboolean prefs_actions_search_func_cb (GtkTreeModel *model, gint column, 98 const gchar *key, GtkTreeIter *iter, 99 gpointer search_data); 100 static void prefs_actions_cancel (GtkWidget *w, 101 gpointer data); 102 static void prefs_actions_ok (GtkWidget *w, 103 gpointer data); 104 105 static GtkListStore* prefs_actions_create_data_store (void); 106 107 static void prefs_actions_list_view_insert_action (GtkWidget *list_view, 108 gint row, 109 gchar *action, 110 gboolean is_valid); 111 static GtkWidget *prefs_actions_list_view_create (void); 112 static void prefs_actions_create_list_view_columns (GtkWidget *list_view); 113 static void prefs_actions_select_row(GtkTreeView *list_view, GtkTreePath *path); 114 115 static void prefs_action_shell_radiobtn_cb(GtkWidget *widget, gpointer data); 116 117 void prefs_actions_open(MainWindow *mainwin) 118 { 119 inc_lock(); 120 121 if (!actions.window) 122 prefs_actions_create(mainwin); 123 124 manage_window_set_transient(GTK_WINDOW(actions.window)); 125 gtk_widget_grab_focus(actions.ok_btn); 126 127 prefs_actions_set_dialog(); 128 129 gtk_widget_show(actions.window); 130 gtk_window_set_modal(GTK_WINDOW(actions.window), TRUE); 131 } 132 133 /*! 134 *\brief Save Gtk object size to prefs dataset 135 */ 136 static void prefs_actions_size_allocate_cb(GtkWidget *widget, 137 GtkAllocation *allocation) 138 { 139 cm_return_if_fail(allocation != NULL); 140 141 gtk_window_get_size(GTK_WINDOW(widget), 142 &prefs_common.actionswin_width, &prefs_common.actionswin_height); 143 } 144 145 static void prefs_actions_create(MainWindow *mainwin) 146 { 147 GtkWidget *window; 148 GtkWidget *vbox; 149 GtkWidget *filter_hbox; 150 GtkWidget *help_btn; 151 GtkWidget *ok_btn; 152 GtkWidget *cancel_btn; 153 GtkWidget *confirm_area; 154 155 GtkWidget *vbox1; 156 GtkWidget *table; 157 158 GtkWidget *shell_radiobtn; 159 160 GtkWidget *name_label; 161 GtkWidget *name_entry; 162 GtkWidget *cmd_label; 163 GtkWidget *cmd_entry; 164 165 GtkWidget *reg_hbox; 166 GtkWidget *btn_hbox; 167 GtkWidget *arrow; 168 GtkWidget *reg_btn; 169 GtkWidget *subst_btn; 170 GtkWidget *del_btn; 171 GtkWidget *clear_btn; 172 173 GtkWidget *cond_hbox; 174 GtkWidget *cond_scrolledwin; 175 GtkWidget *cond_list_view; 176 177 GtkWidget *info_btn; 178 179 GtkWidget *btn_vbox; 180 GtkWidget *spc_vbox; 181 GtkWidget *top_btn; 182 GtkWidget *up_btn; 183 GtkWidget *down_btn; 184 GtkWidget *bottom_btn; 185 static GdkGeometry geometry; 186 187 debug_print("Creating actions configuration window...\n"); 188 189 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "prefs_actions"); 190 191 gtk_container_set_border_width(GTK_CONTAINER (window), 8); 192 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); 193 gtk_window_set_resizable(GTK_WINDOW(window), TRUE); 194 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG); 195 196 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); 197 gtk_widget_show(vbox); 198 gtk_container_add(GTK_CONTAINER(window), vbox); 199 200 gtkut_stock_button_set_create_with_help(&confirm_area, &help_btn, 201 &cancel_btn, NULL, _("_Cancel"), 202 &ok_btn, NULL, _("_OK"), 203 NULL, NULL, NULL); 204 gtk_widget_show(confirm_area); 205 gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0); 206 gtk_widget_grab_default(ok_btn); 207 208 gtk_window_set_title(GTK_WINDOW(window), _("Actions configuration")); 209 g_signal_connect(G_OBJECT(window), "delete_event", 210 G_CALLBACK(prefs_actions_deleted), NULL); 211 g_signal_connect(G_OBJECT(window), "size_allocate", 212 G_CALLBACK(prefs_actions_size_allocate_cb), NULL); 213 g_signal_connect(G_OBJECT(window), "key_press_event", 214 G_CALLBACK(prefs_actions_key_pressed), NULL); 215 MANAGE_WINDOW_SIGNALS_CONNECT(window); 216 g_signal_connect(G_OBJECT(ok_btn), "clicked", 217 G_CALLBACK(prefs_actions_ok), mainwin); 218 g_signal_connect(G_OBJECT(cancel_btn), "clicked", 219 G_CALLBACK(prefs_actions_cancel), NULL); 220 g_signal_connect(G_OBJECT(help_btn), "clicked", 221 G_CALLBACK(manual_open_with_anchor_cb), 222 MANUAL_ANCHOR_ACTIONS); 223 224 vbox1 = gtk_box_new(GTK_ORIENTATION_VERTICAL, VSPACING); 225 gtk_widget_show(vbox1); 226 gtk_box_pack_start(GTK_BOX(vbox), vbox1, TRUE, TRUE, 0); 227 gtk_container_set_border_width(GTK_CONTAINER(vbox1), 2); 228 229 table = gtk_grid_new(); 230 gtk_widget_show(table); 231 gtk_grid_set_row_spacing(GTK_GRID(table), VSPACING_NARROW_2); 232 gtk_grid_set_column_spacing(GTK_GRID(table), 4); 233 gtk_box_pack_start (GTK_BOX (vbox1), table, FALSE, FALSE, 0); 234 235 name_label = gtk_label_new (_("Menu name")); 236 gtk_widget_show (name_label); 237 gtk_label_set_xalign (GTK_LABEL (name_label), 1.0); 238 gtk_grid_attach(GTK_GRID(table), name_label, 0, 0, 1, 1); 239 240 name_entry = gtk_entry_new (); 241 gtk_widget_show (name_entry); 242 gtk_grid_attach(GTK_GRID(table), name_entry, 1, 0, 1, 1); 243 gtk_widget_set_hexpand(name_entry, TRUE); 244 gtk_widget_set_halign(name_entry, GTK_ALIGN_FILL); 245 246 cmd_label = gtk_label_new (_("Command")); 247 gtk_widget_show (cmd_label); 248 gtk_label_set_xalign (GTK_LABEL (cmd_label), 1.0); 249 gtk_grid_attach(GTK_GRID(table), cmd_label, 0, 2, 1, 1); 250 251 cmd_entry = gtk_entry_new (); 252 gtk_widget_show (cmd_entry); 253 gtk_grid_attach(GTK_GRID(table), cmd_entry, 1, 2, 1, 1); 254 gtk_widget_set_hexpand(cmd_entry, TRUE); 255 gtk_widget_set_halign(cmd_entry, GTK_ALIGN_FILL); 256 257 /* radio buttons for filter actions or shell */ 258 filter_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,4); 259 gtk_grid_attach(GTK_GRID(table), filter_hbox, 1, 3, 1, 1); 260 gtk_widget_set_hexpand(filter_hbox, TRUE); 261 gtk_widget_set_halign(filter_hbox, GTK_ALIGN_FILL); 262 gtk_widget_show(filter_hbox); 263 264 shell_radiobtn = gtk_radio_button_new_with_label(NULL, _("Shell command")); 265 gtk_box_pack_start(GTK_BOX(filter_hbox), shell_radiobtn, FALSE, FALSE, 0); 266 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(shell_radiobtn), TRUE); 267 gtk_widget_show(shell_radiobtn); 268 269 g_signal_connect(G_OBJECT(shell_radiobtn), "clicked", 270 G_CALLBACK(prefs_action_shell_radiobtn_cb), NULL); 271 272 /* register / substitute / delete */ 273 274 reg_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); 275 gtk_widget_show(reg_hbox); 276 gtk_box_pack_start(GTK_BOX(vbox1), reg_hbox, FALSE, FALSE, 0); 277 278 arrow = gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_MENU); 279 gtk_widget_show(arrow); 280 gtk_box_pack_start(GTK_BOX(reg_hbox), arrow, FALSE, FALSE, 0); 281 gtk_widget_set_size_request(arrow, -1, 16); 282 283 btn_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); 284 gtk_widget_show(btn_hbox); 285 gtk_box_pack_start(GTK_BOX(reg_hbox), btn_hbox, FALSE, FALSE, 0); 286 287 reg_btn = gtkut_stock_button("list-add", _("_Add")); 288 gtk_widget_show(reg_btn); 289 gtk_box_pack_start(GTK_BOX(btn_hbox), reg_btn, FALSE, TRUE, 0); 290 g_signal_connect(G_OBJECT(reg_btn), "clicked", 291 G_CALLBACK(prefs_actions_register_cb), NULL); 292 CLAWS_SET_TIP(reg_btn, 293 _("Append the new action above to the list")); 294 295 subst_btn = gtkut_get_replace_btn(_("_Replace")); 296 gtk_widget_show(subst_btn); 297 gtk_box_pack_start(GTK_BOX(btn_hbox), subst_btn, FALSE, TRUE, 0); 298 g_signal_connect(G_OBJECT(subst_btn), "clicked", 299 G_CALLBACK(prefs_actions_substitute_cb), NULL); 300 CLAWS_SET_TIP(subst_btn, 301 _("Replace the selected action in list with the action above")); 302 303 del_btn = gtkut_stock_button("list-remove", _("_Remove")); 304 gtk_widget_show(del_btn); 305 gtk_box_pack_start(GTK_BOX(btn_hbox), del_btn, FALSE, TRUE, 0); 306 g_signal_connect(G_OBJECT(del_btn), "clicked", 307 G_CALLBACK(prefs_actions_delete_cb), NULL); 308 CLAWS_SET_TIP(del_btn, 309 _("Delete the selected action from the list")); 310 311 clear_btn = gtkut_stock_button("edit-clear", _("C_lear")); 312 gtk_widget_show (clear_btn); 313 gtk_box_pack_start (GTK_BOX (btn_hbox), clear_btn, FALSE, TRUE, 0); 314 g_signal_connect(G_OBJECT (clear_btn), "clicked", 315 G_CALLBACK(prefs_actions_clear_cb), NULL); 316 CLAWS_SET_TIP(clear_btn, 317 _("Clear all the input fields in the dialog")); 318 319 cond_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); 320 gtk_widget_show(cond_hbox); 321 gtk_box_pack_start(GTK_BOX(vbox1), cond_hbox, TRUE, TRUE, 0); 322 323 cond_scrolledwin = gtk_scrolled_window_new(NULL, NULL); 324 gtk_widget_show(cond_scrolledwin); 325 gtk_widget_set_size_request(cond_scrolledwin, -1, 150); 326 gtk_box_pack_start(GTK_BOX(cond_hbox), cond_scrolledwin, 327 TRUE, TRUE, 0); 328 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (cond_scrolledwin), 329 GTK_POLICY_AUTOMATIC, 330 GTK_POLICY_AUTOMATIC); 331 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(cond_scrolledwin), 332 GTK_SHADOW_ETCHED_IN); 333 334 cond_list_view = prefs_actions_list_view_create(); 335 gtk_widget_show(cond_list_view); 336 gtk_container_add(GTK_CONTAINER (cond_scrolledwin), cond_list_view); 337 338 btn_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); 339 gtk_widget_show(btn_vbox); 340 gtk_box_pack_start(GTK_BOX(cond_hbox), btn_vbox, FALSE, FALSE, 0); 341 342 top_btn = gtkut_stock_button("go-top", _("_Top")); 343 gtk_widget_show(top_btn); 344 gtk_box_pack_start(GTK_BOX(btn_vbox), top_btn, FALSE, FALSE, 0); 345 g_signal_connect(G_OBJECT(top_btn), "clicked", 346 G_CALLBACK(prefs_actions_top_cb), NULL); 347 CLAWS_SET_TIP(top_btn, 348 _("Move the selected action to the top")); 349 350 PACK_SPACER(btn_vbox, spc_vbox, VSPACING_NARROW_2); 351 352 up_btn = gtkut_stock_button("go-up", _("_Up")); 353 gtk_widget_show(up_btn); 354 gtk_box_pack_start(GTK_BOX(btn_vbox), up_btn, FALSE, FALSE, 0); 355 g_signal_connect(G_OBJECT(up_btn), "clicked", 356 G_CALLBACK(prefs_actions_up_cb), NULL); 357 CLAWS_SET_TIP(up_btn, 358 _("Move the selected action up")); 359 360 down_btn = gtkut_stock_button("go-down", _("_Down")); 361 gtk_widget_show(down_btn); 362 gtk_box_pack_start(GTK_BOX(btn_vbox), down_btn, FALSE, FALSE, 0); 363 g_signal_connect(G_OBJECT(down_btn), "clicked", 364 G_CALLBACK(prefs_actions_down_cb), NULL); 365 CLAWS_SET_TIP(down_btn, 366 _("Move selected action down")); 367 368 PACK_SPACER(btn_vbox, spc_vbox, VSPACING_NARROW_2); 369 370 bottom_btn = gtkut_stock_button("go-bottom", _("_Bottom")); 371 gtk_widget_show(bottom_btn); 372 gtk_box_pack_start(GTK_BOX(btn_vbox), bottom_btn, FALSE, FALSE, 0); 373 g_signal_connect(G_OBJECT(bottom_btn), "clicked", 374 G_CALLBACK(prefs_actions_bottom_cb), NULL); 375 CLAWS_SET_TIP(bottom_btn, 376 _("Move the selected action to the bottom")); 377 378 if (!geometry.min_height) { 379 geometry.min_width = 486; 380 geometry.min_height = 322; 381 } 382 383 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &geometry, 384 GDK_HINT_MIN_SIZE); 385 gtk_window_set_default_size(GTK_WINDOW(window), prefs_common.actionswin_width, 386 prefs_common.actionswin_height); 387 388 gtk_widget_show(window); 389 390 actions.window = window; 391 actions.ok_btn = ok_btn; 392 actions.info_btn = info_btn; 393 394 actions.name_entry = name_entry; 395 actions.cmd_entry = cmd_entry; 396 actions.shell_radiobtn = shell_radiobtn; 397 398 actions.actions_list_view = cond_list_view; 399 } 400 401 static void prefs_actions_reset_dialog(void) 402 { 403 gtk_entry_set_text(GTK_ENTRY(actions.name_entry), ""); 404 gtk_entry_set_text(GTK_ENTRY(actions.cmd_entry), ""); 405 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(actions.shell_radiobtn), TRUE); 406 } 407 408 void prefs_actions_read_config(void) 409 { 410 FILE *fp; 411 gchar buf[PREFSBUFSIZE]; 412 gchar *act; 413 414 char rcpath[PATH_MAX]; 415 strlcpy(rcpath, get_rc_dir(), sizeof(rcpath)); 416 strlcat(rcpath, "/actionsrc", sizeof(rcpath)); 417 if ((fp = fopen(rcpath, "r")) == NULL) { 418 warn("open %s", rcpath); 419 return; 420 } 421 422 while (prefs_common.actions_list != NULL) { 423 act = (gchar *)prefs_common.actions_list->data; 424 prefs_common.actions_list = 425 g_slist_remove(prefs_common.actions_list, act); 426 g_free(act); 427 } 428 429 while (fgets(buf, sizeof(buf), fp) != NULL) { 430 const gchar *src_codeset = conv_get_locale_charset_str(); 431 const gchar *dest_codeset = CS_UTF_8; 432 gchar *tmp; 433 434 tmp = conv_codeset_strdup(buf, src_codeset, dest_codeset); 435 if (!tmp) { 436 g_warning("failed to convert character set of action configuration"); 437 tmp = g_strdup(buf); 438 } 439 440 g_strchomp(tmp); 441 act = strstr(tmp, ": "); 442 if (act && act[2] && 443 action_get_type(&act[2]) != ACTION_ERROR) 444 prefs_common.actions_list = 445 g_slist_append(prefs_common.actions_list, 446 tmp); 447 else 448 g_free(tmp); 449 } 450 fclose(fp); 451 } 452 453 void prefs_actions_write_config(void) 454 { 455 PrefFile *pfile; 456 GSList *cur; 457 458 debug_print("Writing actions configuration...\n"); 459 460 char rcpath[PATH_MAX]; 461 strlcpy(rcpath, get_rc_dir(), sizeof(rcpath)); 462 strlcat(rcpath, "/actionsrc", sizeof(rcpath)); 463 if ((pfile= prefs_write_open(rcpath)) == NULL) { 464 warn("write actions configuration file %s", rcpath); 465 return; 466 } 467 468 for (cur = prefs_common.actions_list; cur != NULL; cur = cur->next) { 469 gchar *tmp = (gchar *)cur->data; 470 const gchar *src_codeset = CS_UTF_8; 471 const gchar *dest_codeset = conv_get_locale_charset_str(); 472 gchar *act; 473 474 act = conv_codeset_strdup(tmp, src_codeset, dest_codeset); 475 if (!act) { 476 g_warning("failed to convert character set of action configuration"); 477 act = g_strdup(act); 478 } 479 480 if (fputs(act, pfile->fp) == EOF || 481 fputc('\n', pfile->fp) == EOF) { 482 FILE_OP_ERROR(rcpath, "fputs || fputc"); 483 prefs_file_close_revert(pfile); 484 g_free(act); 485 return; 486 } 487 g_free(act); 488 } 489 prefs_file_close(pfile); 490 } 491 492 static void prefs_actions_clear_list(GtkListStore *list_store) 493 { 494 gtk_list_store_clear(list_store); 495 496 prefs_actions_list_view_insert_action(actions.actions_list_view, 497 -1, _("(New)"), FALSE); 498 } 499 500 static void prefs_actions_set_dialog(void) 501 { 502 GtkListStore *store; 503 GSList *cur; 504 505 store = GTK_LIST_STORE(gtk_tree_view_get_model 506 (GTK_TREE_VIEW(actions.actions_list_view))); 507 508 prefs_actions_clear_list(store); 509 prefs_actions_reset_dialog(); 510 511 for (cur = prefs_common.actions_list; cur != NULL; cur = cur->next) { 512 gchar *action = (gchar *) cur->data; 513 514 prefs_actions_list_view_insert_action(actions.actions_list_view, 515 -1, action, TRUE); 516 } 517 } 518 519 static void prefs_actions_set_list(void) 520 { 521 GtkTreeIter iter; 522 GtkListStore *store; 523 524 g_slist_free(prefs_common.actions_list); 525 prefs_common.actions_list = NULL; 526 527 store = GTK_LIST_STORE(gtk_tree_view_get_model 528 (GTK_TREE_VIEW(actions.actions_list_view))); 529 530 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter)) { 531 do { 532 gchar *action; 533 gboolean is_valid; 534 535 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 536 PREFS_ACTIONS_DATA, &action, 537 PREFS_ACTIONS_VALID, &is_valid, 538 -1); 539 540 if (is_valid) 541 prefs_common.actions_list = 542 g_slist_append(prefs_common.actions_list, 543 action); 544 545 } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(store), 546 &iter)); 547 } 548 } 549 550 #define GET_ENTRY(entry) \ 551 entry_text = gtk_entry_get_text(GTK_ENTRY(entry)) 552 553 static gint prefs_actions_clist_set_row(gint row) 554 { 555 const gchar *entry_text; 556 gint len; 557 gchar action[PREFSBUFSIZE]; 558 gchar *new_action; 559 560 GET_ENTRY(actions.name_entry); 561 if (entry_text[0] == '\0') { 562 alertpanel_error(_("Menu name is not set.")); 563 return -1; 564 } 565 566 if (entry_text[0] == '/') { 567 alertpanel_error(_("A leading '/' is not allowed in the menu name.")); 568 return -1; 569 } 570 571 if (strchr(entry_text, ':')) { 572 alertpanel_error(_("Colon ':' is not allowed in the menu name.")); 573 return -1; 574 } 575 576 strncpy(action, entry_text, PREFSBUFSIZE - 1); 577 578 while (strstr(action, "//")) { 579 char *to_move = strstr(action, "//")+1; 580 char *where = strstr(action, "//"); 581 int old_len = strlen(action); 582 memmove(where, to_move, strlen(to_move)); 583 action[old_len-1] = '\0'; 584 } 585 586 g_strstrip(action); 587 588 /* Keep space for the ': ' delimiter */ 589 len = strlen(action) + 2; 590 if (len >= PREFSBUFSIZE - 1) { 591 alertpanel_error(_("Menu name is too long.")); 592 return -1; 593 } 594 595 strcat(action, ": "); 596 597 GET_ENTRY(actions.cmd_entry); 598 599 if (entry_text[0] == '\0') { 600 alertpanel_error(_("Command-line not set.")); 601 return -1; 602 } 603 604 if (len + strlen(entry_text) >= PREFSBUFSIZE - 1) { 605 alertpanel_error(_("Menu name and command are too long.")); 606 return -1; 607 } 608 609 if (action_get_type(entry_text) == ACTION_ERROR) { 610 gchar *message; 611 message = g_markup_printf_escaped(_("The command\n%s\nhas a syntax error."), 612 entry_text); 613 alertpanel_error("%s", message); 614 g_free(message); 615 return -1; 616 } 617 618 strcat(action, entry_text); 619 620 new_action = g_strdup(action); 621 prefs_actions_list_view_insert_action(actions.actions_list_view, 622 row, new_action, TRUE); 623 624 prefs_actions_set_list(); 625 626 return 0; 627 } 628 629 /* callback functions */ 630 631 static void prefs_actions_register_cb(GtkWidget *w, gpointer data) 632 { 633 prefs_actions_clist_set_row(-1); 634 635 modified = FALSE; 636 modified_list = TRUE; 637 } 638 639 static void prefs_actions_substitute_cb(GtkWidget *w, gpointer data) 640 { 641 gint row; 642 643 row = gtkut_list_view_get_selected_row(actions.actions_list_view); 644 if (row <= 0) 645 return; 646 647 prefs_actions_clist_set_row(row); 648 649 modified = FALSE; 650 modified_list = TRUE; 651 } 652 653 static void prefs_actions_delete_cb(gpointer gtk_action, gpointer data) 654 { 655 GtkTreeIter sel; 656 GtkTreeModel *model; 657 gchar *action; 658 gint row; 659 660 row = gtkut_list_view_get_selected_row(actions.actions_list_view); 661 if (row <= 0) 662 return; 663 664 if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection 665 (GTK_TREE_VIEW(actions.actions_list_view)), 666 &model, &sel)) 667 return; 668 669 if (alertpanel(_("Delete action"), 670 _("Do you really want to delete this action?"), 671 NULL, _("_Cancel"), "edit-delete", _("D_elete"), NULL, NULL, 672 ALERTFOCUS_FIRST) != G_ALERTALTERNATE) 673 return; 674 675 /* XXX: Here's the reason why we need to store the original 676 * pointer: we search the slist for it. */ 677 gtk_tree_model_get(model, &sel, 678 PREFS_ACTIONS_DATA, &action, 679 -1); 680 gtk_list_store_remove(GTK_LIST_STORE(model), &sel); 681 682 prefs_common.actions_list = g_slist_remove(prefs_common.actions_list, 683 action); 684 modified_list = TRUE; 685 } 686 687 static void prefs_actions_delete_all_cb(gpointer gtk_action, gpointer data) 688 { 689 GtkListStore *list_store; 690 691 if (alertpanel(_("Delete all actions"), 692 _("Do you really want to delete all the actions?"), 693 NULL, _("_Cancel"), "edit-delete", _("D_elete"), NULL, NULL, 694 ALERTFOCUS_FIRST) != G_ALERTDEFAULT) 695 return; 696 697 list_store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(actions.actions_list_view))); 698 prefs_actions_clear_list(list_store); 699 modified = FALSE; 700 701 prefs_actions_reset_dialog(); 702 modified_list = TRUE; 703 } 704 705 static void prefs_actions_clear_cb(gpointer gtk_action, gpointer data) 706 { 707 gint row; 708 709 prefs_actions_reset_dialog(); 710 row = gtkut_list_view_get_selected_row(actions.actions_list_view); 711 if (row < 1) 712 modified = FALSE; 713 else 714 modified = TRUE; 715 } 716 717 static void prefs_actions_duplicate_cb(gpointer gtk_action, gpointer data) 718 { 719 gint row; 720 721 row = gtkut_list_view_get_selected_row(actions.actions_list_view); 722 if (row <= 0) 723 return; 724 725 modified_list = !prefs_actions_clist_set_row(-row-2); 726 } 727 728 static void prefs_actions_top_cb(GtkWidget *w, gpointer data) 729 { 730 gint row; 731 GtkTreeIter top, sel; 732 GtkTreeModel *model; 733 734 row = gtkut_list_view_get_selected_row(actions.actions_list_view); 735 if (row <= 1) 736 return; 737 738 model = gtk_tree_view_get_model(GTK_TREE_VIEW(actions.actions_list_view)); 739 740 if (!gtk_tree_model_iter_nth_child(model, &top, NULL, 0) 741 || !gtk_tree_model_iter_nth_child(model, &sel, NULL, row)) 742 return; 743 744 gtk_list_store_move_after(GTK_LIST_STORE(model), &sel, &top); 745 gtkut_list_view_select_row(actions.actions_list_view, 1); 746 modified_list = TRUE; 747 } 748 749 static void prefs_actions_up_cb(GtkWidget *w, gpointer data) 750 { 751 gint row; 752 GtkTreeIter top, sel; 753 GtkTreeModel *model; 754 755 row = gtkut_list_view_get_selected_row(actions.actions_list_view); 756 if (row <= 1) 757 return; 758 759 model = gtk_tree_view_get_model(GTK_TREE_VIEW(actions.actions_list_view)); 760 761 if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row - 1) 762 || !gtk_tree_model_iter_nth_child(model, &sel, NULL, row)) 763 return; 764 765 gtk_list_store_swap(GTK_LIST_STORE(model), &top, &sel); 766 gtkut_list_view_select_row(actions.actions_list_view, row - 1); 767 modified_list = TRUE; 768 } 769 770 static void prefs_actions_down_cb(GtkWidget *w, gpointer data) 771 { 772 gint row, n_rows; 773 GtkTreeIter top, sel; 774 GtkTreeModel *model; 775 776 model = gtk_tree_view_get_model(GTK_TREE_VIEW(actions.actions_list_view)); 777 n_rows = gtk_tree_model_iter_n_children(model, NULL); 778 row = gtkut_list_view_get_selected_row(actions.actions_list_view); 779 if (row < 1 || row >= n_rows - 1) 780 return; 781 782 if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row) 783 || !gtk_tree_model_iter_nth_child(model, &sel, NULL, row + 1)) 784 return; 785 786 gtk_list_store_swap(GTK_LIST_STORE(model), &top, &sel); 787 gtkut_list_view_select_row(actions.actions_list_view, row + 1); 788 modified_list = TRUE; 789 } 790 791 static void prefs_actions_bottom_cb(GtkWidget *w, gpointer data) 792 { 793 gint row, n_rows; 794 GtkTreeIter top, sel; 795 GtkTreeModel *model; 796 797 model = gtk_tree_view_get_model(GTK_TREE_VIEW(actions.actions_list_view)); 798 n_rows = gtk_tree_model_iter_n_children(model, NULL); 799 row = gtkut_list_view_get_selected_row(actions.actions_list_view); 800 if (row < 1 || row >= n_rows - 1) 801 return; 802 803 if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row) 804 || !gtk_tree_model_iter_nth_child(model, &sel, NULL, n_rows - 1)) 805 return; 806 807 gtk_list_store_move_after(GTK_LIST_STORE(model), &top, &sel); 808 gtkut_list_view_select_row(actions.actions_list_view, n_rows - 1); 809 modified_list = TRUE; 810 } 811 812 static gint prefs_actions_deleted(GtkWidget *widget, GdkEventAny *event, 813 gpointer *data) 814 { 815 prefs_actions_cancel(widget, data); 816 return TRUE; 817 } 818 819 static gboolean prefs_actions_key_pressed(GtkWidget *widget, GdkEventKey *event, 820 gpointer data) 821 { 822 GtkWidget *focused = gtkut_get_focused_child(GTK_CONTAINER(widget)); 823 if (focused && GTK_IS_EDITABLE(focused)) 824 modified = TRUE; 825 return FALSE; 826 } 827 828 static gboolean prefs_actions_search_func_cb (GtkTreeModel *model, gint column, const gchar *key, 829 GtkTreeIter *iter, gpointer search_data) 830 { 831 gchar *store_string; 832 gboolean retval; 833 GtkTreePath *path; 834 835 gtk_tree_model_get (model, iter, column, &store_string, -1); 836 837 if (!store_string || !key) 838 return FALSE; 839 840 841 retval = (strncmp (key, store_string, strlen(key)) != 0); 842 843 g_free(store_string); 844 debug_print("selecting row\n"); 845 path = gtk_tree_model_get_path(model, iter); 846 prefs_actions_select_row(GTK_TREE_VIEW(actions.actions_list_view), path); 847 gtk_tree_path_free(path); 848 849 return retval; 850 } 851 static void prefs_actions_cancel(GtkWidget *w, gpointer data) 852 { 853 GtkListStore *store; 854 855 if (modified && alertpanel(_("Entry not saved"), 856 _("The entry was not saved. Close anyway?"), 857 "window-close", _("_Close"), NULL, _("_Continue editing"), 858 NULL, NULL, ALERTFOCUS_SECOND) != G_ALERTDEFAULT) { 859 return; 860 } else if (modified_list && alertpanel(_("Actions list not saved"), 861 _("The actions list has been modified. Close anyway?"), 862 "window-close", _("_Close"), NULL, _("_Continue editing"), 863 NULL, NULL, ALERTFOCUS_SECOND) != G_ALERTDEFAULT) { 864 return; 865 } 866 modified = FALSE; 867 modified_list = FALSE; 868 store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW 869 (actions.actions_list_view))); 870 gtk_list_store_clear(store); 871 prefs_actions_read_config(); 872 gtk_widget_hide(actions.window); 873 gtk_window_set_modal(GTK_WINDOW(actions.window), FALSE); 874 inc_unlock(); 875 } 876 877 static void prefs_actions_ok(GtkWidget *widget, gpointer data) 878 { 879 MainWindow *mainwin = (MainWindow *) data; 880 const GList *list; 881 const GList *iter; 882 MessageView *msgview; 883 Compose *compose; 884 GtkListStore *store; 885 886 if (modified && alertpanel(_("Entry not saved"), 887 _("The entry was not saved. Close anyway?"), 888 "window-close", _("_Close"), NULL, _("_Continue editing"), 889 NULL, NULL, ALERTFOCUS_SECOND) != G_ALERTDEFAULT) { 890 return; 891 } 892 modified = FALSE; 893 modified_list = FALSE; 894 prefs_actions_set_list(); 895 store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW 896 (actions.actions_list_view))); 897 gtk_list_store_clear(store); 898 prefs_actions_write_config(); 899 900 /* Update mainwindow actions menu */ 901 main_window_update_actions_menu(mainwin); 902 903 /* Update separated message view actions menu */ 904 list = messageview_get_msgview_list(); 905 for (iter = list; iter; iter = iter->next) { 906 msgview = (MessageView *) iter->data; 907 messageview_update_actions_menu(msgview); 908 } 909 910 /* Update compose windows actions menu */ 911 list = compose_get_compose_list(); 912 for (iter = list; iter; iter = iter->next) { 913 compose = (Compose *) iter->data; 914 compose_update_actions_menu(compose); 915 } 916 917 /* Update toolbars */ 918 prefs_toolbar_update_action_btns(); 919 920 gtk_widget_hide(actions.window); 921 gtk_window_set_modal(GTK_WINDOW(actions.window), FALSE); 922 inc_unlock(); 923 } 924 925 static GtkListStore* prefs_actions_create_data_store(void) 926 { 927 return gtk_list_store_new(N_PREFS_ACTIONS_COLUMNS, 928 G_TYPE_STRING, 929 G_TYPE_POINTER, 930 G_TYPE_BOOLEAN, 931 -1); 932 } 933 934 static void prefs_actions_list_view_insert_action(GtkWidget *list_view, 935 gint row, 936 gchar *action, 937 gboolean is_valid) 938 { 939 GtkTreeIter iter; 940 GtkTreeIter sibling; 941 GtkListStore *list_store = GTK_LIST_STORE(gtk_tree_view_get_model 942 (GTK_TREE_VIEW(list_view))); 943 944 /* row -1 to add a new rule to store, 945 row >=0 to change an existing row 946 row <-1 insert a new row after (-row-2) 947 */ 948 if (row >= 0 ) { 949 /* modify the existing */ 950 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store), 951 &iter, NULL, row)) 952 row = -1; 953 } else if (row < -1 ) { 954 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store), 955 &sibling, NULL, -row-2)) 956 row = -1; 957 } 958 959 if (row == -1 ) { 960 /* append new */ 961 gtk_list_store_append(list_store, &iter); 962 gtk_list_store_set(list_store, &iter, 963 PREFS_ACTIONS_STRING, action, 964 PREFS_ACTIONS_DATA, action, 965 PREFS_ACTIONS_VALID, is_valid, 966 -1); 967 } else if (row < -1) { 968 /* duplicate */ 969 gtk_list_store_insert_after(list_store, &iter, &sibling); 970 gtk_list_store_set(list_store, &iter, 971 PREFS_ACTIONS_STRING, action, 972 PREFS_ACTIONS_DATA, action, 973 PREFS_ACTIONS_VALID, is_valid, 974 -1); 975 } else { 976 /* change existing */ 977 gchar *old_action; 978 979 gtk_tree_model_get(GTK_TREE_MODEL(list_store), &iter, 980 PREFS_ACTIONS_DATA, &old_action, 981 -1); 982 g_free(old_action); 983 984 gtk_list_store_set(list_store, &iter, 985 PREFS_ACTIONS_STRING, action, 986 PREFS_ACTIONS_DATA, action, 987 -1); 988 } 989 } 990 991 static GtkActionGroup *prefs_actions_popup_action = NULL; 992 static GtkWidget *prefs_actions_popup_menu = NULL; 993 994 static GtkActionEntry prefs_actions_popup_entries[] = 995 { 996 {"PrefsActionsPopup", NULL, "PrefsActionsPopup", NULL, NULL, NULL }, 997 {"PrefsActionsPopup/Delete", NULL, N_("_Delete"), NULL, NULL, G_CALLBACK(prefs_actions_delete_cb) }, 998 {"PrefsActionsPopup/DeleteAll", NULL, N_("Delete _all"), NULL, NULL, G_CALLBACK(prefs_actions_delete_all_cb) }, 999 {"PrefsActionsPopup/Duplicate", NULL, N_("D_uplicate"), NULL, NULL, G_CALLBACK(prefs_actions_duplicate_cb) }, 1000 }; 1001 1002 static void prefs_actions_row_selected(GtkTreeSelection *selection, GtkTreeView *list_view) 1003 { 1004 GtkTreePath *path; 1005 GtkTreeIter iter; 1006 GtkTreeModel *model; 1007 1008 if (!gtk_tree_selection_get_selected(selection, &model, &iter)) 1009 return; 1010 1011 path = gtk_tree_model_get_path(model, &iter); 1012 prefs_actions_select_row(list_view, path); 1013 gtk_tree_path_free(path); 1014 } 1015 1016 static gint prefs_actions_list_btn_pressed(GtkWidget *widget, GdkEventButton *event, 1017 GtkTreeView *list_view) 1018 { 1019 if (event) { 1020 /* left- or right-button click */ 1021 if (event->button == 1 || event->button == 3) { 1022 GtkTreePath *path = NULL; 1023 if (gtk_tree_view_get_path_at_pos( list_view, event->x, event->y, 1024 &path, NULL, NULL, NULL)) { 1025 prefs_actions_select_row(list_view, path); 1026 } 1027 if (path) 1028 gtk_tree_path_free(path); 1029 } 1030 1031 /* right-button click */ 1032 if (event->button == 3) { 1033 GtkTreeModel *model = gtk_tree_view_get_model(list_view); 1034 GtkTreeIter iter; 1035 gboolean non_empty; 1036 gint row; 1037 1038 if (!prefs_actions_popup_menu) { 1039 prefs_actions_popup_action = cm_menu_create_action_group("PrefsActionsPopup", 1040 prefs_actions_popup_entries, G_N_ELEMENTS(prefs_actions_popup_entries), 1041 (gpointer)list_view); 1042 MENUITEM_ADDUI("/Menus", "PrefsActionsPopup", "PrefsActionsPopup", GTK_UI_MANAGER_MENU) 1043 MENUITEM_ADDUI("/Menus/PrefsActionsPopup", "Delete", "PrefsActionsPopup/Delete", GTK_UI_MANAGER_MENUITEM) 1044 MENUITEM_ADDUI("/Menus/PrefsActionsPopup", "DeleteAll", "PrefsActionsPopup/DeleteAll", GTK_UI_MANAGER_MENUITEM) 1045 MENUITEM_ADDUI("/Menus/PrefsActionsPopup", "Duplicate", "PrefsActionsPopup/Duplicate", GTK_UI_MANAGER_MENUITEM) 1046 prefs_actions_popup_menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM( 1047 gtk_ui_manager_get_widget(gtkut_ui_manager(), "/Menus/PrefsActionsPopup")) ); 1048 } 1049 1050 /* grey out some popup menu items if there is no selected row */ 1051 row = gtkut_list_view_get_selected_row(GTK_WIDGET(list_view)); 1052 cm_menu_set_sensitive("PrefsActionsPopup/Delete", (row > 0)); 1053 cm_menu_set_sensitive("PrefsActionsPopup/Duplicate", (row > 0)); 1054 1055 /* grey out seom popup menu items if there is no row 1056 (not counting the (New) one at row 0) */ 1057 non_empty = gtk_tree_model_get_iter_first(model, &iter); 1058 if (non_empty) 1059 non_empty = gtk_tree_model_iter_next(model, &iter); 1060 cm_menu_set_sensitive("PrefsActionsPopup/DeleteAll", non_empty); 1061 1062 gtk_menu_popup_at_pointer(GTK_MENU(prefs_actions_popup_menu), NULL); 1063 } 1064 } 1065 return FALSE; 1066 } 1067 1068 static gboolean prefs_actions_list_popup_menu(GtkWidget *widget, gpointer data) 1069 { 1070 GtkTreeView *list_view = (GtkTreeView *)data; 1071 GdkEventButton event; 1072 1073 event.button = 3; 1074 event.time = gtk_get_current_event_time(); 1075 1076 prefs_actions_list_btn_pressed(NULL, &event, list_view); 1077 1078 return TRUE; 1079 } 1080 1081 static GtkWidget *prefs_actions_list_view_create(void) 1082 { 1083 GtkTreeView *list_view; 1084 GtkTreeSelection *selector; 1085 GtkTreeModel *model; 1086 1087 model = GTK_TREE_MODEL(prefs_actions_create_data_store()); 1088 list_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(model)); 1089 g_object_unref(model); 1090 1091 g_signal_connect(G_OBJECT(list_view), "popup-menu", 1092 G_CALLBACK(prefs_actions_list_popup_menu), list_view); 1093 g_signal_connect(G_OBJECT(list_view), "button-press-event", 1094 G_CALLBACK(prefs_actions_list_btn_pressed), list_view); 1095 1096 gtk_tree_view_set_reorderable(list_view, TRUE); 1097 1098 selector = gtk_tree_view_get_selection(list_view); 1099 gtk_tree_selection_set_mode(selector, GTK_SELECTION_BROWSE); 1100 g_signal_connect(G_OBJECT(selector), "changed", 1101 G_CALLBACK(prefs_actions_row_selected), list_view); 1102 1103 /* create the columns */ 1104 prefs_actions_create_list_view_columns(GTK_WIDGET(list_view)); 1105 1106 return GTK_WIDGET(list_view); 1107 } 1108 1109 static void prefs_actions_create_list_view_columns(GtkWidget *list_view) 1110 { 1111 GtkTreeViewColumn *column; 1112 GtkCellRenderer *renderer; 1113 1114 renderer = gtk_cell_renderer_text_new(); 1115 column = gtk_tree_view_column_new_with_attributes 1116 (_("Current actions"), 1117 renderer, 1118 "text", PREFS_ACTIONS_STRING, 1119 NULL); 1120 gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column); 1121 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(list_view), prefs_actions_search_func_cb , NULL, NULL); 1122 } 1123 1124 #define ENTRY_SET_TEXT(entry, str) \ 1125 gtk_entry_set_text(GTK_ENTRY(entry), str ? str : "") 1126 1127 static void prefs_actions_select_row(GtkTreeView *list_view, GtkTreePath *path) 1128 { 1129 GtkTreeModel *model = gtk_tree_view_get_model(list_view); 1130 GtkTreeSelection *selection; 1131 gchar *action; 1132 gchar *cmd; 1133 gchar buf[PREFSBUFSIZE]; 1134 GtkTreeIter iter; 1135 gboolean is_valid; 1136 1137 if (!model || !path || !gtk_tree_model_get_iter(model, &iter, path)) 1138 return; 1139 1140 /* select row */ 1141 selection = gtk_tree_view_get_selection(list_view); 1142 gtk_tree_selection_select_path(selection, path); 1143 1144 gtk_tree_model_get(model, &iter, 1145 PREFS_ACTIONS_VALID, &is_valid, 1146 PREFS_ACTIONS_DATA, &action, 1147 -1); 1148 if (!is_valid) { 1149 prefs_actions_reset_dialog(); 1150 return; 1151 } 1152 1153 strncpy(buf, action, PREFSBUFSIZE - 1); 1154 buf[PREFSBUFSIZE - 1] = '\0'; 1155 cmd = strstr(buf, ": "); 1156 1157 if (cmd && cmd[2]) 1158 ENTRY_SET_TEXT(actions.cmd_entry, &cmd[2]); 1159 else 1160 return; 1161 1162 *cmd = '\0'; 1163 gtk_entry_set_text(GTK_ENTRY(actions.name_entry), buf); 1164 1165 if (g_str_has_prefix(&cmd[2], "%as{") == TRUE) 1166 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( 1167 actions.filter_radiobtn), TRUE); 1168 else 1169 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( 1170 actions.shell_radiobtn), TRUE); 1171 1172 return; 1173 } 1174 1175 static void prefs_action_shell_radiobtn_cb(GtkWidget *widget, gpointer data) 1176 { 1177 if (actions.cmd_entry) 1178 gtk_widget_set_sensitive(actions.cmd_entry, TRUE); 1179 if (actions.info_btn) 1180 gtk_widget_set_sensitive(actions.info_btn, TRUE); 1181 } 1182 1183 void prefs_actions_rename_path(const gchar *old_path, const gchar *new_path) 1184 { } 1185 1186 gint prefs_actions_find_by_name(const gchar *name) 1187 { 1188 GSList *act = prefs_common.actions_list; 1189 gchar *action_name, *action_p; 1190 gint action_nb = 0; 1191 1192 for (; act != NULL; act = act->next) { 1193 action_name = g_strdup((gchar *)act->data); 1194 action_p = strstr(action_name, ": "); 1195 action_p[0] = 0x00; 1196 1197 if (g_utf8_collate(name, action_name) == 0) { 1198 g_free(action_name); 1199 return action_nb; 1200 } 1201 1202 g_free(action_name); 1203 action_nb++; 1204 } 1205 1206 return -1; 1207 }