r65: Added Link menu item to create symlinks.
[rox-filer/ma.git] / ROX-Filer / src / menu.c
blob5419f947a99a05b49ee47781706a7de1c73ca1de
1 /* vi: set cindent:
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * By Thomas Leonard, <tal197@ecs.soton.ac.uk>.
6 */
8 /* menu.c - code for handling the popup menu */
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <sys/wait.h>
15 #include <fcntl.h>
16 #include <unistd.h>
17 #include <errno.h>
18 #include <string.h>
20 #include <gdk/gdkx.h>
21 #include <gtk/gtk.h>
23 #include "apps.h"
24 #include "action.h"
25 #include "filer.h"
26 #include "type.h"
27 #include "support.h"
28 #include "gui_support.h"
29 #include "options.h"
30 #include "choices.h"
31 #include "savebox.h"
33 #define C_ "<control>"
35 #define MENU_MARGIN 32
37 GtkAccelGroup *filer_keys;
38 GtkAccelGroup *panel_keys;
40 /* Options */
41 static GtkWidget *xterm_here_entry;
42 static char *xterm_here_value;
44 /* Static prototypes */
45 static void position_menu(GtkMenu *menu, gint *x, gint *y, gpointer data);
46 static void menu_closed(GtkWidget *widget);
47 static void items_sensitive(GtkWidget *menu, int from, int n, gboolean state);
48 static char *load_xterm_here(char *data);
50 static void hidden(gpointer data, guint action, GtkWidget *widget);
51 static void refresh(gpointer data, guint action, GtkWidget *widget);
53 static void copy_item(gpointer data, guint action, GtkWidget *widget);
54 static void rename_item(gpointer data, guint action, GtkWidget *widget);
55 static void link_item(gpointer data, guint action, GtkWidget *widget);
56 static void help(gpointer data, guint action, GtkWidget *widget);
57 static void mount(gpointer data, guint action, GtkWidget *widget);
58 static void delete(gpointer data, guint action, GtkWidget *widget);
60 static void select_all(gpointer data, guint action, GtkWidget *widget);
61 static void clear_selection(gpointer data, guint action, GtkWidget *widget);
62 static void show_options(gpointer data, guint action, GtkWidget *widget);
63 static void new_directory(gpointer data, guint action, GtkWidget *widget);
64 static void xterm_here(gpointer data, guint action, GtkWidget *widget);
65 static void open_parent(gpointer data, guint action, GtkWidget *widget);
67 static void open_as_dir(gpointer data, guint action, GtkWidget *widget);
68 static void close_panel(gpointer data, guint action, GtkWidget *widget);
70 static GtkWidget *filer_menu; /* The popup filer menu */
71 static GtkWidget *filer_file_item; /* The File '' label */
72 static GtkWidget *filer_file_menu; /* The File '' menu */
73 static GtkWidget *panel_menu; /* The popup panel menu */
74 static GtkWidget *panel_file_item; /* The File '' label */
75 static GtkWidget *panel_file_menu; /* The File '' menu */
77 static GtkItemFactoryEntry filer_menu_def[] = {
78 {"/Display", NULL, NULL, 0, "<Branch>"},
79 {"/Display/Large Icons", NULL, NULL, 0, "<RadioItem>"},
80 {"/Display/Small Icons", NULL, NULL, 0, "/Display/Large Icons"},
81 {"/Display/Full Info", NULL, NULL, 0, "/Display/Large Icons"},
82 {"/Display/Separator", NULL, NULL, 0, "<Separator>"},
83 {"/Display/Sort by Name", NULL, NULL, 0, "<RadioItem>"},
84 {"/Display/Sort by Type", NULL, NULL, 0, "/Display/Sort by Name"},
85 {"/Display/Sort by Date", NULL, NULL, 0, "/Display/Sort by Name"},
86 {"/Display/Sort by Size", NULL, NULL, 0, "/Display/Sort by Name"},
87 {"/Display/Sort by Owner", NULL, NULL, 0, "/Display/Sort by Name"},
88 {"/Display/Separator", NULL, NULL, 0, "<Separator>"},
89 {"/Display/Show Hidden", C_"H", hidden, 0, "<ToggleItem>"},
90 {"/Display/Refresh", C_"L", refresh, 0, NULL},
91 {"/File", NULL, NULL, 0, "<Branch>"},
92 {"/File/Copy...", NULL, copy_item, 0, NULL},
93 {"/File/Rename...", NULL, rename_item, 0, NULL},
94 {"/File/Link...", NULL, link_item, 0, NULL},
95 {"/File/Help", "F1", help, 0, NULL},
96 {"/File/Info", NULL, NULL, 0, NULL},
97 {"/File/Separator", NULL, NULL, 0, "<Separator>"},
98 {"/File/Mount", C_"M", mount, 0, NULL},
99 {"/File/Delete", C_"X", delete, 0, NULL},
100 {"/File/Disk Usage", C_"U", NULL, 0, NULL},
101 {"/File/Permissions", NULL, NULL, 0, NULL},
102 {"/File/Touch", NULL, NULL, 0, NULL},
103 {"/File/Find", NULL, NULL, 0, NULL},
104 {"/Select All", C_"A", select_all, 0, NULL},
105 {"/Clear Selection", C_"Z", clear_selection, 0, NULL},
106 {"/Options...", NULL, show_options, 0, NULL},
107 {"/New directory", NULL, new_directory, 0, NULL},
108 {"/Xterm here", NULL, xterm_here, 0, NULL},
109 {"/Open parent", NULL, open_parent, 0, NULL},
112 static GtkItemFactoryEntry panel_menu_def[] = {
113 {"/Display", NULL, NULL, 0, "<Branch>"},
114 {"/Display/Large Icons", NULL, NULL, 0, "<RadioItem>"},
115 {"/Display/Small Icons", NULL, NULL, 0, "/Display/Large Icons"},
116 {"/Display/Full Info", NULL, NULL, 0, "/Display/Large Icons"},
117 {"/Display/Separator", NULL, NULL, 0, "<Separator>"},
118 {"/Display/Sort by Name", NULL, NULL, 0, "<RadioItem>"},
119 {"/Display/Sort by Type", NULL, NULL, 0, "/Display/Sort by Name"},
120 {"/Display/Sort by Date", NULL, NULL, 0, "/Display/Sort by Name"},
121 {"/Display/Sort by Size", NULL, NULL, 0, "/Display/Sort by Name"},
122 {"/Display/Sort by Owner", NULL, NULL, 0, "/Display/Sort by Name"},
123 {"/Display/Separator", NULL, NULL, 0, "<Separator>"},
124 {"/Display/Show Hidden", NULL, hidden, 0, "<ToggleItem>"},
125 {"/Display/Refresh", NULL, refresh, 0, NULL},
126 {"/File", NULL, NULL, 0, "<Branch>"},
127 {"/File/Help", NULL, help, 0, NULL},
128 {"/File/Delete", NULL, delete, 0, NULL},
129 {"/Open as directory", NULL, open_as_dir, 0, NULL},
130 {"/Close panel", NULL, close_panel, 0, NULL},
133 void menu_init()
135 GtkItemFactory *item_factory;
136 char *menurc;
137 GList *items;
139 filer_keys = gtk_accel_group_new();
140 item_factory = gtk_item_factory_new(GTK_TYPE_MENU,
141 "<filer>",
142 filer_keys);
143 gtk_item_factory_create_items(item_factory,
144 sizeof(filer_menu_def) / sizeof(*filer_menu_def),
145 filer_menu_def,
146 NULL);
147 filer_menu = gtk_item_factory_get_widget(item_factory, "<filer>");
148 filer_file_menu = gtk_item_factory_get_widget(item_factory,
149 "<filer>/File");
150 items = gtk_container_children(GTK_CONTAINER(filer_menu));
151 filer_file_item = GTK_BIN(g_list_nth(items, 1)->data)->child;
152 g_list_free(items);
154 panel_keys = gtk_accel_group_new();
155 item_factory = gtk_item_factory_new(GTK_TYPE_MENU,
156 "<panel>",
157 panel_keys);
158 gtk_item_factory_create_items(item_factory,
159 sizeof(panel_menu_def) / sizeof(*panel_menu_def),
160 panel_menu_def,
161 NULL);
162 panel_menu = gtk_item_factory_get_widget(item_factory, "<panel>");
163 panel_file_menu = gtk_item_factory_get_widget(item_factory,
164 "<panel>/File");
165 items = gtk_container_children(GTK_CONTAINER(panel_menu));
166 panel_file_item = GTK_BIN(g_list_nth(items, 1)->data)->child;
167 g_list_free(items);
169 menurc = choices_find_path_load("menus");
170 if (menurc)
171 gtk_item_factory_parse_rc(menurc);
173 gtk_signal_connect(GTK_OBJECT(panel_menu), "unmap_event",
174 GTK_SIGNAL_FUNC(menu_closed), NULL);
175 gtk_signal_connect(GTK_OBJECT(filer_menu), "unmap_event",
176 GTK_SIGNAL_FUNC(menu_closed), NULL);
178 gtk_accel_group_lock(panel_keys);
180 xterm_here_value = g_strdup("xterm");
181 option_register("xterm_here", load_xterm_here);
184 /* Build up some option widgets to go in the options dialog, but don't
185 * fill them in yet.
187 GtkWidget *create_menu_options()
189 GtkWidget *table, *label;
191 table = gtk_table_new(2, 2, FALSE);
192 gtk_container_set_border_width(GTK_CONTAINER(table), 4);
194 label = gtk_label_new("To set the keyboard short-cuts you simply open "
195 "the menu over a filer window, move the pointer over "
196 "the item you want to use and press a key. The key "
197 "will appear next to the menu item and you can just "
198 "press that key without opening the menu in future. "
199 "To save the current menu short-cuts for next time, "
200 "click the Save button at the bottom of this window.");
201 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
202 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 2, 0, 1);
204 label = gtk_label_new("'Xterm here' program:");
205 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
206 xterm_here_entry = gtk_entry_new();
207 gtk_table_attach_defaults(GTK_TABLE(table), xterm_here_entry,
208 1, 2, 1, 2);
210 return table;
213 static char *load_xterm_here(char *data)
215 g_free(xterm_here_value);
216 xterm_here_value = g_strdup(data);
217 return NULL;
220 void menu_update_options()
222 gtk_entry_set_text(GTK_ENTRY(xterm_here_entry), xterm_here_value);
225 void menu_set_options()
227 g_free(xterm_here_value);
228 xterm_here_value = g_strdup(gtk_entry_get_text(
229 GTK_ENTRY(xterm_here_entry)));
232 void menu_save_options()
234 char *menurc;
236 menurc = choices_find_path_save("menus");
237 if (menurc)
238 gtk_item_factory_dump_rc(menurc, NULL, TRUE);
240 option_write("xterm_here", xterm_here_value);
244 static void items_sensitive(GtkWidget *menu, int from, int n, gboolean state)
246 GList *items, *item;
248 items = gtk_container_children(GTK_CONTAINER(menu));
250 item = g_list_nth(items, from);
251 while (item && n--)
253 gtk_widget_set_sensitive(GTK_BIN(item->data)->child, state);
254 item = item->next;
257 g_list_free(items);
260 static void position_menu(GtkMenu *menu, gint *x, gint *y, gpointer data)
262 int *pos = (int *) data;
263 int swidth, sheight;
264 GtkRequisition requisition;
266 gdk_window_get_size(GDK_ROOT_PARENT(), &swidth, &sheight);
267 /* XXX: GDK sometimes seems to get the height wrong... */
268 if (sheight < 2)
270 g_print("ROX-Filer: Root window height reported as %d\n",
271 sheight);
272 sheight = 768;
275 gtk_widget_size_request(GTK_WIDGET(menu), &requisition);
277 if (pos[0] == -1)
278 *x = swidth - MENU_MARGIN - requisition.width;
279 else if (pos[0] == -2)
280 *x = MENU_MARGIN;
281 else
282 *x = pos[0] - (requisition.width >> 2);
284 if (pos[1] == -1)
285 *y = sheight - MENU_MARGIN - requisition.height;
286 else if (pos[1] == -2)
287 *y = MENU_MARGIN;
288 else
289 *y = pos[1] - (requisition.height >> 2);
291 *x = CLAMP(*x, 0, swidth - requisition.width);
292 *y = CLAMP(*y, 0, sheight - requisition.height);
295 void show_filer_menu(FilerWindow *filer_window, GdkEventButton *event,
296 int item)
298 GString *buffer;
299 GtkWidget *file_label, *file_menu;
300 FileItem *file_item;
301 int pos[] = {event->x_root, event->y_root};
303 window_with_focus = filer_window;
305 if (filer_window->panel)
307 switch (filer_window->panel_side)
309 case TOP: pos[1] = -2; break;
310 case BOTTOM: pos[1] = -1; break;
311 case LEFT: pos[0] = -2; break;
312 case RIGHT: pos[0] = -1; break;
316 if (filer_window->panel)
318 collection_clear_selection(filer_window->collection);
319 panel_set_timeout(NULL, 0);
322 if (filer_window->collection->number_selected == 0 && item >= 0)
324 collection_select_item(filer_window->collection, item);
325 filer_window->temp_item_selected = TRUE;
327 else
328 filer_window->temp_item_selected = FALSE;
330 if (filer_window->panel)
332 file_label = panel_file_item;
333 file_menu = panel_file_menu;
335 else
337 file_label = filer_file_item;
338 file_menu = filer_file_menu;
341 buffer = g_string_new(NULL);
342 switch (filer_window->collection->number_selected)
344 case 0:
345 g_string_assign(buffer, "<nothing selected>");
346 items_sensitive(file_menu, 0, 5, FALSE);
347 items_sensitive(file_menu, 6, -1, FALSE);
348 gtk_widget_set_sensitive(file_label, FALSE);
349 break;
350 case 1:
351 items_sensitive(file_menu, 0, 5, TRUE);
352 items_sensitive(file_menu, 6, -1, TRUE);
353 gtk_widget_set_sensitive(file_label, TRUE);
354 file_item = selected_item(filer_window->collection);
355 g_string_sprintf(buffer, "%s '%s'",
356 basetype_name(file_item),
357 file_item->leafname);
358 break;
359 default:
360 items_sensitive(file_menu, 0, 5, FALSE);
361 items_sensitive(file_menu, 6, -1, TRUE);
362 gtk_widget_set_sensitive(file_label, TRUE);
363 g_string_sprintf(buffer, "%d items",
364 filer_window->collection->number_selected);
365 break;
368 gtk_label_set_text(GTK_LABEL(file_label), buffer->str);
370 g_string_free(buffer, TRUE);
372 gtk_menu_popup(filer_window->panel ? GTK_MENU(panel_menu)
373 : GTK_MENU(filer_menu),
374 NULL, NULL, position_menu,
375 (gpointer) pos, event->button, event->time);
378 static void menu_closed(GtkWidget *widget)
380 if (window_with_focus == NULL)
381 return; /* Close panel item chosen? */
383 if (window_with_focus->temp_item_selected)
385 collection_clear_selection(window_with_focus->collection);
386 window_with_focus->temp_item_selected = FALSE;
390 /* Actions */
392 static void hidden(gpointer data, guint action, GtkWidget *widget)
394 g_return_if_fail(window_with_focus != NULL);
396 window_with_focus->show_hidden = !window_with_focus->show_hidden;
397 scan_dir(window_with_focus);
400 static void refresh(gpointer data, guint action, GtkWidget *widget)
402 g_return_if_fail(window_with_focus != NULL);
404 scan_dir(window_with_focus);
407 static void delete(gpointer data, guint action, GtkWidget *widget)
409 g_return_if_fail(window_with_focus != NULL);
411 action_delete(window_with_focus);
414 static gboolean copy_cb(char *initial, char *path)
416 char *new_dir, *slash;
417 int len;
418 GString *command;
419 gboolean retval = TRUE;
421 slash = strrchr(path, '/');
422 if (!slash)
424 report_error("ROX-Filer", "Missing '/' in new pathname");
425 return FALSE;
428 if (access(path, F_OK) == 0)
430 report_error("ROX-Filer",
431 "An item with this name already exists");
432 return FALSE;
435 len = slash - path;
436 new_dir = g_malloc(len + 1);
437 memcpy(new_dir, path, len);
438 new_dir[len] = '\0';
440 command = g_string_new(NULL);
441 g_string_sprintf(command, "cp -a %s %s", initial, path);
443 if (system(command->str))
445 g_string_append(command, " failed!");
446 report_error("ROX-Filer", command->str);
447 retval = FALSE;
450 g_string_free(command, TRUE);
452 refresh_dirs(new_dir);
453 return retval;
456 static void copy_item(gpointer data, guint action, GtkWidget *widget)
458 Collection *collection;
460 g_return_if_fail(window_with_focus != NULL);
462 collection = window_with_focus->collection;
463 if (collection->number_selected != 1)
464 report_error("ROX-Filer", "You must select a single "
465 "item to copy");
466 else
468 FileItem *item = selected_item(collection);
470 savebox_show(window_with_focus, "Copy",
471 window_with_focus->path, item->leafname,
472 item->image, copy_cb);
476 static gboolean rename_cb(char *initial, char *path)
478 if (rename(initial, path))
480 report_error("ROX-Filer: rename()", g_strerror(errno));
481 return FALSE;
483 return TRUE;
486 static void rename_item(gpointer data, guint action, GtkWidget *widget)
488 Collection *collection;
490 g_return_if_fail(window_with_focus != NULL);
492 collection = window_with_focus->collection;
493 if (collection->number_selected != 1)
494 report_error("ROX-Filer", "You must select a single "
495 "item to rename");
496 else
498 FileItem *item = selected_item(collection);
500 savebox_show(window_with_focus, "Rename",
501 window_with_focus->path, item->leafname,
502 item->image, rename_cb);
506 static gboolean link_cb(char *initial, char *path)
508 if (symlink(initial, path))
510 report_error("ROX-Filer: symlink()", g_strerror(errno));
511 return FALSE;
513 return TRUE;
516 static void link_item(gpointer data, guint action, GtkWidget *widget)
518 Collection *collection;
520 g_return_if_fail(window_with_focus != NULL);
522 collection = window_with_focus->collection;
523 if (collection->number_selected != 1)
524 report_error("ROX-Filer", "You must select a single "
525 "item to link");
526 else
528 FileItem *item = selected_item(collection);
530 savebox_show(window_with_focus, "Symlink",
531 window_with_focus->path, item->leafname,
532 item->image, link_cb);
536 static void help(gpointer data, guint action, GtkWidget *widget)
538 Collection *collection;
539 FileItem *item;
541 g_return_if_fail(window_with_focus != NULL);
543 collection = window_with_focus->collection;
544 if (collection->number_selected != 1)
546 report_error("ROX-Filer", "You must select a single "
547 "item to get help on");
548 return;
550 item = selected_item(collection);
551 switch (item->base_type)
553 case TYPE_FILE:
554 if (item->flags & ITEM_FLAG_EXEC_FILE)
555 report_error("Executable file",
556 "This is a file with an eXecute bit "
557 "set - it can be run as a program.");
558 else
559 report_error("File",
560 "This is a file. It contains stored data. Try "
561 "running file(1) on it to find out what kind "
562 "of data it contains.");
563 break;
564 case TYPE_DIRECTORY:
565 if (item->flags & ITEM_FLAG_APPDIR)
566 app_show_help(
567 make_path(window_with_focus->path,
568 item->leafname)->str);
569 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
570 report_error("Mount point",
571 "A mount point is a directory which another "
572 "filing system can be mounted on. Everything "
573 "on the mounted filesystem then appears to be "
574 "inside the directory.");
575 else
576 report_error("Directory",
577 "This is a directory. It contains an index to "
578 "other items - open it to see the list.");
579 break;
580 case TYPE_CHAR_DEVICE:
581 case TYPE_BLOCK_DEVICE:
582 report_error("Device file",
583 "Device files allow you to read from or write "
584 "to a device driver as though it was an "
585 "ordinary file.");
586 break;
587 case TYPE_PIPE:
588 report_error("Named pipe",
589 "Pipes allow different programs to "
590 "communicate. One program writes data to the "
591 "pipe while another one reads it out again.");
592 break;
593 case TYPE_SOCKET:
594 report_error("Socket",
595 "Sockets allow processes to communicate.");
596 break;
597 default:
598 report_error("Unknown type",
599 "I couldn't find out what kind of file this "
600 "is. Maybe it doesn't exist anymore or you "
601 "don't have search permission on the directory "
602 "it's in?");
603 break;
608 static void mount(gpointer data, guint action, GtkWidget *widget)
610 FileItem *item;
611 int i;
612 Collection *collection;
613 char *error = NULL;
614 int count = 0;
616 g_return_if_fail(window_with_focus != NULL);
618 collection = window_with_focus->collection;
620 for (i = 0; i < collection->number_of_items; i++)
621 if (collection->items[i].selected)
623 item = (FileItem *) collection->items[i].data;
624 if (item->flags & ITEM_FLAG_MOUNT_POINT)
626 char *argv[] = {"mount", NULL, NULL};
627 int child;
629 count++;
630 if (item->flags & ITEM_FLAG_MOUNTED)
631 argv[0] = "umount";
632 argv[1] = make_path(window_with_focus->path,
633 item->leafname)->str;
634 child = spawn(argv);
635 if (child)
636 waitpid(child, NULL, 0);
637 else
638 error = "Failed to run mount/umount";
641 if (count)
642 scan_dir(window_with_focus);
643 else if (!error)
644 error = "You must select some mount points first!";
646 if (error)
647 report_error("ROX-Filer", error);
650 static void select_all(gpointer data, guint action, GtkWidget *widget)
652 g_return_if_fail(window_with_focus != NULL);
654 collection_select_all(window_with_focus->collection);
655 window_with_focus->temp_item_selected = FALSE;
658 static void clear_selection(gpointer data, guint action, GtkWidget *widget)
660 g_return_if_fail(window_with_focus != NULL);
662 collection_clear_selection(window_with_focus->collection);
663 window_with_focus->temp_item_selected = FALSE;
666 static void show_options(gpointer data, guint action, GtkWidget *widget)
668 g_return_if_fail(window_with_focus != NULL);
670 options_show(window_with_focus);
673 static gboolean new_directory_cb(char *initial, char *path)
675 if (mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO))
677 report_error("mkdir", g_strerror(errno));
678 return FALSE;
680 return TRUE;
683 static void new_directory(gpointer data, guint action, GtkWidget *widget)
685 g_return_if_fail(window_with_focus != NULL);
687 savebox_show(window_with_focus, "Create directory",
688 window_with_focus->path, "NewDir",
689 default_pixmap + TYPE_DIRECTORY, new_directory_cb);
692 static void xterm_here(gpointer data, guint action, GtkWidget *widget)
694 char *argv[] = {xterm_here_value, NULL};
696 g_return_if_fail(window_with_focus != NULL);
698 if (!spawn_full(argv, window_with_focus->path, 0))
699 report_error("ROX-Filer", "Failed to fork() child "
700 "process");
703 static void open_parent(gpointer data, guint action, GtkWidget *widget)
705 g_return_if_fail(window_with_focus != NULL);
707 filer_opendir(make_path(window_with_focus->path, "/..")->str,
708 FALSE, BOTTOM);
711 static void open_as_dir(gpointer data, guint action, GtkWidget *widget)
713 g_return_if_fail(window_with_focus != NULL);
715 filer_opendir(window_with_focus->path, FALSE, BOTTOM);
718 static void close_panel(gpointer data, guint action, GtkWidget *widget)
720 g_return_if_fail(window_with_focus != NULL);
722 gtk_widget_destroy(window_with_focus->window);