- Set LIBS instead of LDFLAGS (LIBS are appended while LDFLAGS are prepended
[gliv.git] / src / actions.c
blob58600b99381267977d976eb0e2e7044f34c29c6c
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 * See the COPYING file for license information.
18 * Guillaume Chazarain <guichaz@gmail.com>
21 /************************
22 * User-defined actions *
23 ************************/
25 #include <stdio.h> /* FILE, fprintf() */
26 #include <string.h> /* strchr(), memset() */
27 #include <stdlib.h> /* system() */
28 #include <sys/wait.h> /* WEXITSTATUS */
30 #include "gliv.h"
31 #include "actions.h"
32 #include "mnemonics.h"
33 #include "gliv-image.h"
34 #include "files_list.h"
35 #include "callbacks.h"
36 #include "windows.h"
37 #include "messages.h"
38 #include "next_image.h"
40 extern GlivImage *current_image;
42 typedef struct {
43 gchar *name;
44 gchar *command;
45 } action;
47 static GPtrArray *actions_array = NULL;
49 static GString *build_command_line(const gchar * command,
50 const gchar * filename)
52 GString *cmd_line = g_string_new("");
53 const gchar *ptr, *remaining = command;
54 gchar *quoted, *tmp;
56 for (;;) {
57 ptr = strchr(remaining, '%');
58 if (ptr == NULL) {
59 g_string_append(cmd_line, remaining);
60 return cmd_line;
63 g_string_append_len(cmd_line, remaining, ptr - remaining);
64 ptr++;
65 remaining = ptr + 1;
67 switch (*ptr) {
68 case 'd':
69 tmp = g_path_get_dirname(filename);
70 quoted = g_shell_quote(tmp);
71 g_free(tmp);
72 g_string_append(cmd_line, quoted);
73 g_free(quoted);
74 break;
76 case 'b':
77 tmp = g_path_get_basename(filename);
78 quoted = g_shell_quote(tmp);
79 g_free(tmp);
80 g_string_append(cmd_line, quoted);
81 g_free(quoted);
82 break;
84 case 'f':
85 quoted = g_shell_quote(filename);
86 g_string_append(cmd_line, quoted);
87 g_free(quoted);
88 break;
90 case '%':
91 g_string_append_c(cmd_line, '%');
92 break;
94 default:
95 g_string_append_len(cmd_line, ptr - 1, 2);
96 break;
101 static gint action_on_filename(action * a, const gchar * filename)
103 gint ret;
104 GString *cmd_line = build_command_line(a->command, filename);
106 ret = system(cmd_line->str);
107 g_string_free(cmd_line, TRUE);
109 return ret;
112 static void process_system_retval(action * a, gint retval)
114 GtkWidget *msg;
115 gchar *str;
117 if (retval == 0)
118 return;
120 if (retval < 0) {
121 str = _("Failed to create a new process");
122 msg = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL,
123 GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
124 "%s", str);
125 } else {
126 str = _("The action '%s' terminated with a non-null return code: %d");
127 msg = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL,
128 GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
129 str, a->name, WEXITSTATUS(retval));
132 run_modal_dialog(GTK_DIALOG(msg));
133 gtk_widget_destroy(msg);
136 static gboolean action_current(action * a)
138 gint ret;
139 gpointer stat_data;
141 if (current_image == NULL || current_image->node == NULL)
142 return FALSE;
144 stat_data = stat_loaded_files(FALSE);
146 ret = action_on_filename(a, current_image->node->data);
147 process_system_retval(a, ret);
149 reload_changed_files(stat_data);
151 return FALSE;
154 static gboolean action_every(action * a)
156 GList *node;
157 gint ret = 0;
158 gpointer stat_data;
159 GlivImage *before = current_image;
161 stat_data = stat_loaded_files(FALSE);
163 for (node = get_list_head(); node != NULL; node = node->next) {
164 gint new_ret = action_on_filename(a, node->data);
165 if (new_ret != 0)
166 ret = new_ret;
167 process_events();
170 process_system_retval(a, ret);
172 if (before == current_image)
173 reload_changed_files(stat_data);
174 else {
175 /* Don't bother checking files changes if the user changed the image */
176 g_free(stat_data);
179 return FALSE;
182 static void make_menu(GtkAccelGroup * accel_group, GtkMenuItem * menu_item,
183 GCallback cb, const gchar * menu_name)
185 const gchar *name;
186 GtkMenuItem *item;
187 GtkMenu *menu = GTK_MENU(gtk_menu_new());
188 gchar *accel_path;
189 gint i;
191 gtk_menu_item_deselect(menu_item);
192 gtk_menu_set_accel_group(menu, accel_group);
193 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), gtk_tearoff_menu_item_new());
194 push_mnemonics();
196 for (i = 0; i < actions_array->len; i++) {
197 action *a = g_ptr_array_index(actions_array, i);
199 name = add_mnemonic(a->name);
200 item = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(name));
201 gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(item));
203 g_signal_connect_swapped(item, "activate", cb, a);
205 accel_path = g_strconcat("<GLiv>/Commands/", menu_name, "/", a->name,
206 NULL);
208 gtk_accel_map_add_entry(accel_path, 0, 0);
209 gtk_menu_item_set_accel_path(item, accel_path);
210 g_free(accel_path);
213 gtk_menu_item_set_submenu(menu_item, GTK_WIDGET(menu));
214 gtk_widget_show_all(GTK_WIDGET(menu_item));
216 pop_mnemonics();
219 void init_actions(GtkAccelGroup * the_accel_group,
220 GtkMenuItem * current_image_menu_item,
221 GtkMenuItem * every_image_menu_item)
223 static GtkAccelGroup *accel_group = NULL;
224 static GtkMenuItem *current_image_item = NULL;
225 static GtkMenuItem *every_image_item = NULL;
227 if (the_accel_group != NULL) {
228 /* First time */
229 accel_group = the_accel_group;
230 add_action(_("Open in Gimp"), "gimp-remote -- %f");
233 if (current_image_menu_item != NULL)
234 current_image_item = current_image_menu_item;
236 if (every_image_menu_item != NULL)
237 every_image_item = every_image_menu_item;
239 if (actions_array == NULL)
240 actions_array = g_ptr_array_new();
242 make_menu(accel_group, current_image_item, G_CALLBACK(action_current),
243 "Action on the current image");
245 make_menu(accel_group, every_image_item, G_CALLBACK(action_every),
246 "Action on every image");
250 /*** GUI part ***/
252 #include "glade_actions.h"
254 enum {
255 COLUMN_NAME,
256 COLUMN_COMMAND,
257 N_COLUMNS
260 static GtkListStore *list_store = NULL;
261 static GtkTreeIter iter;
262 static gboolean has_selection = FALSE;
264 static void get_list_row(GtkTreeIter * where, gchar ** name, gchar ** command)
266 GValue value;
268 memset(&value, 0, sizeof(GValue));
269 if (name != NULL) {
270 gtk_tree_model_get_value(GTK_TREE_MODEL(list_store), where,
271 COLUMN_NAME, &value);
273 *name = g_strdup(g_value_peek_pointer(&value));
274 g_value_unset(&value);
277 if (command != NULL) {
278 gtk_tree_model_get_value(GTK_TREE_MODEL(list_store), where,
279 COLUMN_COMMAND, &value);
281 *command = g_strdup(g_value_peek_pointer(&value));
282 g_value_unset(&value);
286 static gboolean has_action_by_name(const gchar * name)
288 gchar *action_name = NULL;
289 GtkTreeIter iter;
291 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(list_store), &iter)) {
292 do {
293 get_list_row(&iter, &action_name, NULL);
294 if (g_str_equal(name, action_name))
295 break;
297 g_free(action_name);
298 action_name = NULL;
299 } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store), &iter));
302 g_free(action_name);
303 return action_name != NULL;
306 static gboolean edit_action_dialog(gchar ** name, gchar ** command)
309 GtkWidget *dialog = create_edit_action_dialog();
310 GtkEntry *name_entry, *command_entry;
311 gboolean duplicate = FALSE;
312 gint response;
313 gboolean ok = FALSE;
315 name_entry = g_object_get_data(G_OBJECT(dialog), "name_entry");
316 command_entry = g_object_get_data(G_OBJECT(dialog), "command_entry");
318 if (*name != NULL)
319 gtk_entry_set_text(name_entry, *name);
321 if (*command != NULL)
322 gtk_entry_set_text(command_entry, *command);
324 response = run_modal_dialog(GTK_DIALOG(dialog));
325 if (response == GTK_RESPONSE_OK) {
326 gchar *new_name;
328 new_name = gtk_editable_get_chars(GTK_EDITABLE(name_entry), 0, -1);
329 if (*name == NULL || g_str_equal(new_name, *name) == FALSE) {
330 duplicate = has_action_by_name(new_name);
331 if (duplicate) {
332 GtkWidget *msg;
333 gchar *str = _("The action name '%s' is already used");
334 msg = gtk_message_dialog_new(NULL,
335 GTK_DIALOG_MODAL,
336 GTK_MESSAGE_WARNING,
337 GTK_BUTTONS_CLOSE, str, new_name);
338 run_modal_dialog(GTK_DIALOG(msg));
339 gtk_widget_destroy(msg);
343 if (!duplicate) {
344 g_free(*name);
345 g_free(*command);
347 *command = gtk_editable_get_chars(GTK_EDITABLE(command_entry),
348 0, -1);
349 *name = new_name;
350 ok = TRUE;
351 } else
352 g_free(new_name);
355 gtk_widget_destroy(dialog);
356 return ok;
359 static gboolean on_add_button_clicked(void)
361 gchar *name = NULL, *command = NULL;
362 gboolean ok;
364 ok = edit_action_dialog(&name, &command);
366 if (ok && name[0] != '\0' && command[0] != '\0') {
367 GtkTreeIter new_iter;
369 if (has_selection)
370 gtk_list_store_insert_after(list_store, &new_iter, &iter);
371 else
372 gtk_list_store_append(list_store, &new_iter);
374 gtk_list_store_set(list_store, &new_iter,
375 COLUMN_NAME, name, COLUMN_COMMAND, command, -1);
378 g_free(name);
379 g_free(command);
380 return FALSE;
383 static gboolean on_property_button_clicked(void)
386 gchar *name, *command;
387 gint ok;
389 get_list_row(&iter, &name, &command);
390 ok = edit_action_dialog(&name, &command);
392 if (ok && name[0] != '\0' && command[0] != '\0')
393 gtk_list_store_set(list_store, &iter,
394 COLUMN_NAME, name, COLUMN_COMMAND, command, -1);
395 g_free(name);
396 g_free(command);
397 return FALSE;
400 static gboolean on_delete_button_clicked(void)
402 gtk_list_store_remove(list_store, &iter);
403 return FALSE;
406 static gboolean selection_changed(GtkTreeSelection * selection,
407 GtkWidget * dialog)
409 GtkWidget *property_button, *delete_button;
411 has_selection = gtk_tree_selection_get_selected(selection, NULL, &iter);
413 property_button = g_object_get_data(G_OBJECT(dialog), "property_button");
414 delete_button = g_object_get_data(G_OBJECT(dialog), "delete_button");
416 if (GTK_WIDGET_IS_SENSITIVE(property_button) != has_selection) {
417 gtk_widget_set_sensitive(property_button, has_selection);
418 gtk_widget_set_sensitive(delete_button, has_selection);
421 return FALSE;
424 #include "glade_actions.c"
426 static void add_column(GtkTreeView * tree_view, const gchar * name,
427 gint column_id)
429 GtkTreeViewColumn *column;
430 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
432 column = gtk_tree_view_column_new_with_attributes(name, renderer,
433 "text", column_id, NULL);
435 gtk_tree_view_column_set_resizable(column, TRUE);
436 gtk_tree_view_column_set_reorderable(column, TRUE);
437 gtk_tree_view_column_set_sort_column_id(column, column_id);
439 gtk_tree_view_append_column(tree_view, column);
442 static GtkListStore *make_tree_view(GtkWidget * dialog)
444 GtkTreeView *tree_view = g_object_get_data(G_OBJECT(dialog), "treeview");
445 GtkListStore *list_store;
446 GtkTreeIter iter;
447 GtkTreeSelection *selection;
448 gint i;
450 list_store = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
452 for (i = 0; i < actions_array->len; i++) {
453 action *a = g_ptr_array_index(actions_array, i);
455 gtk_list_store_append(list_store, &iter);
456 gtk_list_store_set(list_store, &iter,
457 COLUMN_NAME, a->name,
458 COLUMN_COMMAND, a->command, -1);
461 gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(list_store));
463 add_column(tree_view, _("Name"), COLUMN_NAME);
464 add_column(tree_view, _("Command"), COLUMN_COMMAND);
466 selection = gtk_tree_view_get_selection(tree_view);
467 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
468 g_signal_connect(selection, "changed", G_CALLBACK(selection_changed),
469 dialog);
471 return list_store;
474 static gboolean read_store(GtkTreeModel * unused_model,
475 GtkTreePath * unused_path, GtkTreeIter * iter)
477 gchar *name, *command;
478 action *a = g_new(action, 1);
480 get_list_row(iter, &name, &command);
482 a->name = name;
483 a->command = command;
485 g_ptr_array_add(actions_array, a);
487 return FALSE;
490 static void destroy_actions(void)
492 gint i;
494 for (i = 0; i < actions_array->len; i++) {
495 action *a = g_ptr_array_index(actions_array, i);
497 g_free(a->name);
498 g_free(a->command);
499 g_free(a);
502 g_ptr_array_free(actions_array, TRUE);
503 actions_array = NULL;
506 gboolean edit_actions(void)
508 GtkWidget *dialog = create_actions_dialog();
509 gint response;
511 list_store = make_tree_view(dialog);
512 response = run_modal_dialog(GTK_DIALOG(dialog));
513 gtk_widget_destroy(dialog);
515 if (response == GTK_RESPONSE_OK) {
516 destroy_actions();
517 actions_array = g_ptr_array_new();
518 gtk_tree_model_foreach(GTK_TREE_MODEL(list_store),
519 (GtkTreeModelForeachFunc) read_store, NULL);
520 init_actions(NULL, NULL, NULL);
523 g_object_unref(list_store);
524 list_store = NULL;
525 has_selection = FALSE;
527 return FALSE;
530 /*** glivrc ***/
532 void add_action(const gchar * name, const gchar * command)
534 gint i;
535 action *a;
537 if (actions_array == NULL)
538 actions_array = g_ptr_array_new();
540 for (i = 0; i < actions_array->len; i++) {
541 a = g_ptr_array_index(actions_array, i);
543 if (g_str_equal(a->name, name)) {
544 g_free(a->command);
545 a->command = g_strdup(command);
546 return;
550 a = g_new(action, 1);
551 a->name = g_strdup(name);
552 a->command = g_strdup(command);
554 g_ptr_array_add(actions_array, a);
557 void write_actions(FILE * file)
559 gint i;
560 action *a;
562 if (actions_array == NULL)
563 return;
565 for (i = 0; i < actions_array->len; i++) {
566 a = g_ptr_array_index(actions_array, i);
568 fprintf(file, "action_name = %s\n", a->name);
569 fprintf(file, "action_command = %s\n\n", a->command);