r210: Added 'Permissions' (chmod) feature.
[rox-filer.git] / ROX-Filer / src / menu.c
blob28457194cd05e5a01a44f1d72ff6a2197cdeaa0f
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2000, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* menu.c - code for handling the popup menu */
24 #include <config.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <sys/wait.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <errno.h>
33 #include <string.h>
35 #include <gdk/gdkx.h>
36 #include <gtk/gtk.h>
38 #include "run.h"
39 #include "action.h"
40 #include "filer.h"
41 #include "type.h"
42 #include "support.h"
43 #include "gui_support.h"
44 #include "options.h"
45 #include "choices.h"
46 #include "savebox.h"
47 #include "mount.h"
48 #include "minibuffer.h"
50 #define C_ "<control>"
52 #define MENU_MARGIN 32
54 GtkAccelGroup *filer_keys;
55 GtkAccelGroup *panel_keys;
57 static GtkWidget *popup_menu = NULL; /* Currently open menu */
59 static gint updating_menu = 0; /* Non-zero => ignore activations */
61 /* Options */
62 static GtkWidget *xterm_here_entry;
63 static char *xterm_here_value;
65 /* Static prototypes */
67 static void position_menu(GtkMenu *menu, gint *x, gint *y, gpointer data);
68 static void menu_closed(GtkWidget *widget);
69 static void items_sensitive(GtkWidget *menu, int from, int n, gboolean state);
70 static char *load_xterm_here(char *data);
72 /* Note that for these callbacks none of the arguments are used. */
73 static void not_yet(gpointer data, guint action, GtkWidget *widget);
75 static void large(gpointer data, guint action, GtkWidget *widget);
76 static void small(gpointer data, guint action, GtkWidget *widget);
77 static void full_info(gpointer data, guint action, GtkWidget *widget);
79 static void sort_name(gpointer data, guint action, GtkWidget *widget);
80 static void sort_type(gpointer data, guint action, GtkWidget *widget);
81 static void sort_size(gpointer data, guint action, GtkWidget *widget);
82 static void sort_date(gpointer data, guint action, GtkWidget *widget);
84 static void hidden(gpointer data, guint action, GtkWidget *widget);
85 static void refresh(gpointer data, guint action, GtkWidget *widget);
87 static void copy_item(gpointer data, guint action, GtkWidget *widget);
88 static void rename_item(gpointer data, guint action, GtkWidget *widget);
89 static void link_item(gpointer data, guint action, GtkWidget *widget);
90 static void open_file(gpointer data, guint action, GtkWidget *widget);
91 static void help(gpointer data, guint action, GtkWidget *widget);
92 static void show_file_info(gpointer data, guint action, GtkWidget *widget);
93 static void mount(gpointer data, guint action, GtkWidget *widget);
94 static void delete(gpointer data, guint action, GtkWidget *widget);
95 static void usage(gpointer data, guint action, GtkWidget *widget);
96 static void chmod_items(gpointer data, guint action, GtkWidget *widget);
98 static void select_all(gpointer data, guint action, GtkWidget *widget);
99 static void clear_selection(gpointer data, guint action, GtkWidget *widget);
100 static void show_options(gpointer data, guint action, GtkWidget *widget);
101 static void new_directory(gpointer data, guint action, GtkWidget *widget);
102 static void xterm_here(gpointer data, guint action, GtkWidget *widget);
104 static void open_parent_same(gpointer data, guint action, GtkWidget *widget);
105 static void open_parent(gpointer data, guint action, GtkWidget *widget);
106 static void new_window(gpointer data, guint action, GtkWidget *widget);
107 static void close_window(gpointer data, guint action, GtkWidget *widget);
108 static void enter_path(gpointer data, guint action, GtkWidget *widget);
109 static void rox_help(gpointer data, guint action, GtkWidget *widget);
111 static void open_as_dir(gpointer data, guint action, GtkWidget *widget);
112 static void close_panel(gpointer data, guint action, GtkWidget *widget);
114 static GtkWidget *create_options();
115 static void update_options();
116 static void set_options();
117 static void save_options();
119 static OptionsSection options =
121 "Menu options",
122 create_options,
123 update_options,
124 set_options,
125 save_options
129 static GtkWidget *filer_menu; /* The popup filer menu */
130 static GtkWidget *filer_file_item; /* The File '' label */
131 static GtkWidget *filer_file_menu; /* The File '' menu */
132 static GtkWidget *filer_hidden_menu; /* The Show Hidden item */
133 static GtkWidget *panel_menu; /* The popup panel menu */
134 static GtkWidget *panel_file_item; /* The File '' label */
135 static GtkWidget *panel_file_menu; /* The File '' menu */
136 static GtkWidget *panel_hidden_menu; /* The Show Hidden item */
138 static gint screen_width, screen_height;
140 static GtkItemFactoryEntry filer_menu_def[] = {
141 {"/Display", NULL, NULL, 0, "<Branch>"},
142 {"/Display/Large Icons", NULL, large, 0, NULL},
143 {"/Display/Small Icons", NULL, small, 0, NULL},
144 {"/Display/Full Info", NULL, full_info, 0, NULL},
145 {"/Display/Separator", NULL, NULL, 0, "<Separator>"},
146 {"/Display/Sort by Name", NULL, sort_name, 0, NULL},
147 {"/Display/Sort by Type", NULL, sort_type, 0, NULL},
148 {"/Display/Sort by Date", NULL, sort_date, 0, NULL},
149 {"/Display/Sort by Size", NULL, sort_size, 0, NULL},
150 {"/Display/Separator", NULL, NULL, 0, "<Separator>"},
151 {"/Display/Show Hidden", C_"H", hidden, 0, "<ToggleItem>"},
152 {"/Display/Refresh", C_"L", refresh, 0, NULL},
153 {"/File", NULL, NULL, 0, "<Branch>"},
154 {"/File/Copy...", NULL, copy_item, 0, NULL},
155 {"/File/Rename...", NULL, rename_item, 0, NULL},
156 {"/File/Link...", NULL, link_item, 0, NULL},
157 {"/File/Shift Open", NULL, open_file, 0, NULL},
158 {"/File/Help", "F1", help, 0, NULL},
159 {"/File/Info", "I", show_file_info, 0, NULL},
160 {"/File/Separator", NULL, NULL, 0, "<Separator>"},
161 {"/File/Mount", "M", mount, 0, NULL},
162 {"/File/Delete", C_"X", delete, 0, NULL},
163 {"/File/Disk Usage", "U", usage, 0, NULL},
164 {"/File/Permissions", NULL, chmod_items, 0, NULL},
165 {"/File/Touch", NULL, not_yet, 0, NULL},
166 {"/File/Find", NULL, not_yet, 0, NULL},
167 {"/Select All", C_"A", select_all, 0, NULL},
168 {"/Clear Selection", C_"Z", clear_selection, 0, NULL},
169 {"/Options...", NULL, show_options, 0, NULL},
170 {"/New Directory...", NULL, new_directory, 0, NULL},
171 {"/Xterm Here", NULL, xterm_here, 0, NULL},
172 {"/Window", NULL, NULL, 0, "<Branch>"},
173 {"/Window/Parent, New Window", NULL, open_parent, 0, NULL},
174 {"/Window/Parent, Same Window", NULL, open_parent_same, 0, NULL},
175 {"/Window/New Window", NULL, new_window, 0, NULL},
176 {"/Window/Close Window", C_"Q", close_window, 0, NULL},
177 {"/Window/Enter Path", NULL, enter_path, 0, NULL},
178 {"/Window/Separator", NULL, NULL, 0, "<Separator>"},
179 {"/Window/Show ROX-Filer help", NULL, rox_help, 0, NULL},
182 static GtkItemFactoryEntry panel_menu_def[] = {
183 {"/Display", NULL, NULL, 0, "<Branch>"},
184 {"/Display/Sort by Name", NULL, sort_name, 0, NULL},
185 {"/Display/Sort by Type", NULL, sort_type, 0, NULL},
186 {"/Display/Sort by Date", NULL, sort_date, 0, NULL},
187 {"/Display/Sort by Size", NULL, sort_size, 0, NULL},
188 {"/Display/Separator", NULL, NULL, 0, "<Separator>"},
189 {"/Display/Show Hidden", NULL, hidden, 0, "<ToggleItem>"},
190 {"/Display/Refresh", NULL, refresh, 0, NULL},
191 {"/File", NULL, NULL, 0, "<Branch>"},
192 {"/File/Help", NULL, help, 0, NULL},
193 {"/File/Info", NULL, show_file_info, 0, NULL},
194 {"/File/Delete", NULL, delete, 0, NULL},
195 {"/Open as directory", NULL, open_as_dir, 0, NULL},
196 {"/Options...", NULL, show_options, 0, NULL},
197 {"/Close panel", NULL, close_panel, 0, NULL},
198 {"/Separator", NULL, NULL, 0, "<Separator>"},
199 {"/Show ROX-Filer help", NULL, rox_help, 0, NULL},
202 typedef struct _FileStatus FileStatus;
204 /* This is for the 'file(1) says...' thing */
205 struct _FileStatus
207 int fd; /* FD to read from, -1 if closed */
208 int input; /* Input watcher tag if fd valid */
209 GtkLabel *label; /* Widget to output to */
210 gboolean start; /* No output yet */
213 void menu_init()
215 GtkItemFactory *item_factory;
216 char *menurc;
217 GList *items;
219 /* This call starts returning strange values after a while, so get
220 * the result here during init.
222 gdk_window_get_size(GDK_ROOT_PARENT(), &screen_width, &screen_height);
224 filer_keys = gtk_accel_group_new();
225 item_factory = gtk_item_factory_new(GTK_TYPE_MENU,
226 "<filer>",
227 filer_keys);
228 gtk_item_factory_create_items(item_factory,
229 sizeof(filer_menu_def) / sizeof(*filer_menu_def),
230 filer_menu_def,
231 NULL);
232 filer_menu = gtk_item_factory_get_widget(item_factory, "<filer>");
233 filer_file_menu = gtk_item_factory_get_widget(item_factory,
234 "<filer>/File");
235 filer_hidden_menu = gtk_item_factory_get_widget(item_factory,
236 "<filer>/Display/Show Hidden");
237 items = gtk_container_children(GTK_CONTAINER(filer_menu));
238 filer_file_item = GTK_BIN(g_list_nth(items, 1)->data)->child;
239 g_list_free(items);
241 panel_keys = gtk_accel_group_new();
242 item_factory = gtk_item_factory_new(GTK_TYPE_MENU,
243 "<panel>",
244 panel_keys);
245 gtk_item_factory_create_items(item_factory,
246 sizeof(panel_menu_def) / sizeof(*panel_menu_def),
247 panel_menu_def,
248 NULL);
249 panel_menu = gtk_item_factory_get_widget(item_factory, "<panel>");
250 panel_file_menu = gtk_item_factory_get_widget(item_factory,
251 "<panel>/File");
252 panel_hidden_menu = gtk_item_factory_get_widget(item_factory,
253 "<panel>/Display/Show Hidden");
254 items = gtk_container_children(GTK_CONTAINER(panel_menu));
255 panel_file_item = GTK_BIN(g_list_nth(items, 1)->data)->child;
256 g_list_free(items);
258 menurc = choices_find_path_load("menus");
259 if (menurc)
260 gtk_item_factory_parse_rc(menurc);
262 gtk_accel_group_lock(panel_keys);
264 gtk_signal_connect(GTK_OBJECT(filer_menu), "unmap_event",
265 GTK_SIGNAL_FUNC(menu_closed), NULL);
266 gtk_signal_connect(GTK_OBJECT(panel_menu), "unmap_event",
267 GTK_SIGNAL_FUNC(menu_closed), NULL);
268 gtk_signal_connect(GTK_OBJECT(filer_file_menu), "unmap_event",
269 GTK_SIGNAL_FUNC(menu_closed), NULL);
271 options_sections = g_slist_prepend(options_sections, &options);
272 xterm_here_value = g_strdup("xterm");
273 option_register("xterm_here", load_xterm_here);
276 /* Build up some option widgets to go in the options dialog, but don't
277 * fill them in yet.
279 static GtkWidget *create_options()
281 GtkWidget *table, *label;
283 table = gtk_table_new(2, 2, FALSE);
284 gtk_container_set_border_width(GTK_CONTAINER(table), 4);
286 label = gtk_label_new("To set the keyboard short-cuts you simply open "
287 "the menu over a filer window, move the pointer over "
288 "the item you want to use and press a key. The key "
289 "will appear next to the menu item and you can just "
290 "press that key without opening the menu in future. "
291 "To save the current menu short-cuts for next time, "
292 "click the Save button at the bottom of this window.");
293 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
294 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 2, 0, 1);
296 label = gtk_label_new("'Xterm here' program:");
297 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
298 xterm_here_entry = gtk_entry_new();
299 gtk_table_attach_defaults(GTK_TABLE(table), xterm_here_entry,
300 1, 2, 1, 2);
302 return table;
305 static char *load_xterm_here(char *data)
307 g_free(xterm_here_value);
308 xterm_here_value = g_strdup(data);
309 return NULL;
312 static void update_options()
314 gtk_entry_set_text(GTK_ENTRY(xterm_here_entry), xterm_here_value);
317 static void set_options()
319 g_free(xterm_here_value);
320 xterm_here_value = g_strdup(gtk_entry_get_text(
321 GTK_ENTRY(xterm_here_entry)));
324 static void save_options()
326 char *menurc;
328 menurc = choices_find_path_save("menus", TRUE);
329 if (menurc)
330 gtk_item_factory_dump_rc(menurc, NULL, TRUE);
332 option_write("xterm_here", xterm_here_value);
336 static void items_sensitive(GtkWidget *menu, int from, int n, gboolean state)
338 GList *items, *item;
340 items = gtk_container_children(GTK_CONTAINER(menu));
342 item = g_list_nth(items, from);
343 while (item && n--)
345 gtk_widget_set_sensitive(GTK_BIN(item->data)->child, state);
346 item = item->next;
349 g_list_free(items);
352 static void position_menu(GtkMenu *menu, gint *x, gint *y, gpointer data)
354 int *pos = (int *) data;
355 GtkRequisition requisition;
357 gtk_widget_size_request(GTK_WIDGET(menu), &requisition);
359 if (pos[0] == -1)
360 *x = screen_width - MENU_MARGIN - requisition.width;
361 else if (pos[0] == -2)
362 *x = MENU_MARGIN;
363 else
364 *x = pos[0] - (requisition.width >> 2);
366 if (pos[1] == -1)
367 *y = screen_height - MENU_MARGIN - requisition.height;
368 else if (pos[1] == -2)
369 *y = MENU_MARGIN;
370 else
371 *y = pos[1] - (requisition.height >> 2);
373 *x = CLAMP(*x, 0, screen_width - requisition.width);
374 *y = CLAMP(*y, 0, screen_height - requisition.height);
377 void show_filer_menu(FilerWindow *filer_window, GdkEventButton *event,
378 int item)
380 GString *buffer;
381 GtkWidget *file_label, *file_menu;
382 DirItem *file_item;
383 int pos[2];
385 updating_menu++;
387 pos[0] = event->x_root;
388 pos[1] = event->y_root;
390 window_with_focus = filer_window;
392 switch (filer_window->panel_type)
394 case PANEL_TOP:
395 pos[1] = -2;
396 break;
397 case PANEL_BOTTOM:
398 pos[1] = -1;
399 break;
400 default:
401 break;
404 if (filer_window->panel_type)
405 collection_clear_selection(filer_window->collection); /* ??? */
407 if (filer_window->collection->number_selected == 0 && item >= 0)
409 collection_select_item(filer_window->collection, item);
410 filer_window->temp_item_selected = TRUE;
412 else
413 filer_window->temp_item_selected = FALSE;
415 if (filer_window->panel_type)
417 file_label = panel_file_item;
418 file_menu = panel_file_menu;
419 gtk_check_menu_item_set_active(
420 GTK_CHECK_MENU_ITEM(panel_hidden_menu),
421 filer_window->show_hidden);
423 else
425 file_label = filer_file_item;
426 file_menu = filer_file_menu;
427 gtk_check_menu_item_set_active(
428 GTK_CHECK_MENU_ITEM(filer_hidden_menu),
429 filer_window->show_hidden);
432 buffer = g_string_new(NULL);
433 switch (filer_window->collection->number_selected)
435 case 0:
436 g_string_assign(buffer, "Next Click");
437 items_sensitive(file_menu, 0, 6, TRUE);
438 break;
439 case 1:
440 items_sensitive(file_menu, 0, 6, TRUE);
441 file_item = selected_item(filer_window->collection);
442 g_string_sprintf(buffer, "%s '%s'",
443 basetype_name(file_item),
444 file_item->leafname);
445 break;
446 default:
447 items_sensitive(file_menu, 0, 6, FALSE);
448 g_string_sprintf(buffer, "%d items",
449 filer_window->collection->number_selected);
450 break;
453 gtk_label_set_text(GTK_LABEL(file_label), buffer->str);
455 g_string_free(buffer, TRUE);
457 if (filer_window->panel_type)
458 popup_menu = panel_menu;
459 else
460 popup_menu = (event->state & GDK_CONTROL_MASK)
461 ? filer_file_menu
462 : filer_menu;
464 updating_menu--;
466 gtk_menu_popup(GTK_MENU(popup_menu), NULL, NULL, position_menu,
467 (gpointer) pos, event->button, event->time);
470 static void menu_closed(GtkWidget *widget)
472 if (window_with_focus == NULL || widget != popup_menu)
473 return; /* Close panel item chosen? */
475 if (window_with_focus->temp_item_selected)
477 collection_clear_selection(window_with_focus->collection);
478 window_with_focus->temp_item_selected = FALSE;
482 void target_callback(Collection *collection, gint item, gpointer real_fn)
484 g_return_if_fail(window_with_focus != NULL);
485 g_return_if_fail(window_with_focus->collection == collection);
486 g_return_if_fail(real_fn != NULL);
488 collection_wink_item(collection, item);
489 collection_clear_selection(collection);
490 collection_select_item(collection, item);
491 ((CollectionTargetFunc)real_fn)(NULL, 0, collection);
492 if (item < collection->number_of_items)
493 collection_unselect_item(collection, item);
496 /* Actions */
498 /* Fake action to warn when a menu item does nothing */
499 static void not_yet(gpointer data, guint action, GtkWidget *widget)
501 delayed_error("ROX-Filer", "Sorry, that feature isn't implemented yet");
504 static void large(gpointer data, guint action, GtkWidget *widget)
506 g_return_if_fail(window_with_focus != NULL);
508 filer_style_set(window_with_focus, LARGE_ICONS);
511 static void small(gpointer data, guint action, GtkWidget *widget)
513 g_return_if_fail(window_with_focus != NULL);
515 filer_style_set(window_with_focus, SMALL_ICONS);
518 static void full_info(gpointer data, guint action, GtkWidget *widget)
520 g_return_if_fail(window_with_focus != NULL);
522 filer_style_set(window_with_focus, FULL_INFO);
525 static void sort_name(gpointer data, guint action, GtkWidget *widget)
527 g_return_if_fail(window_with_focus != NULL);
529 filer_set_sort_fn(window_with_focus, sort_by_name);
532 static void sort_type(gpointer data, guint action, GtkWidget *widget)
534 g_return_if_fail(window_with_focus != NULL);
536 filer_set_sort_fn(window_with_focus, sort_by_type);
539 static void sort_date(gpointer data, guint action, GtkWidget *widget)
541 g_return_if_fail(window_with_focus != NULL);
543 filer_set_sort_fn(window_with_focus, sort_by_date);
546 static void sort_size(gpointer data, guint action, GtkWidget *widget)
548 g_return_if_fail(window_with_focus != NULL);
550 filer_set_sort_fn(window_with_focus, sort_by_size);
553 static void hidden(gpointer data, guint action, GtkWidget *widget)
555 if (updating_menu)
556 return;
558 g_return_if_fail(window_with_focus != NULL);
560 filer_set_hidden(window_with_focus, !window_with_focus->show_hidden);
563 static void refresh(gpointer data, guint action, GtkWidget *widget)
565 g_return_if_fail(window_with_focus != NULL);
567 full_refresh();
568 update_dir(window_with_focus, TRUE);
571 static void mount(gpointer data, guint action, GtkWidget *widget)
573 g_return_if_fail(window_with_focus != NULL);
575 if (window_with_focus->collection->number_selected == 0)
576 collection_target(window_with_focus->collection,
577 target_callback, mount);
578 else
579 action_mount(window_with_focus, NULL);
582 static void delete(gpointer data, guint action, GtkWidget *widget)
584 g_return_if_fail(window_with_focus != NULL);
586 if (window_with_focus->collection->number_selected == 0)
587 collection_target(window_with_focus->collection,
588 target_callback, delete);
589 else
590 action_delete(window_with_focus);
593 static void usage(gpointer data, guint action, GtkWidget *widget)
595 g_return_if_fail(window_with_focus != NULL);
597 if (window_with_focus->collection->number_selected == 0)
598 collection_target(window_with_focus->collection,
599 target_callback, usage);
600 else
601 action_usage(window_with_focus);
604 static void chmod_items(gpointer data, guint action, GtkWidget *widget)
606 g_return_if_fail(window_with_focus != NULL);
608 if (window_with_focus->collection->number_selected == 0)
609 collection_target(window_with_focus->collection,
610 target_callback, chmod_items);
611 else
612 action_chmod(window_with_focus);
615 static gboolean copy_cb(char *initial, char *path)
617 char *new_dir, *slash;
618 int len;
619 GString *command;
620 gboolean retval = TRUE;
622 slash = strrchr(path, '/');
623 if (!slash)
625 report_error("ROX-Filer", "Missing '/' in new pathname");
626 return FALSE;
629 if (access(path, F_OK) == 0)
631 report_error("ROX-Filer",
632 "An item with this name already exists");
633 return FALSE;
636 len = slash - path;
637 new_dir = g_malloc(len + 1);
638 memcpy(new_dir, path, len);
639 new_dir[len] = '\0';
641 command = g_string_new(NULL);
642 g_string_sprintf(command, "cp -a %s %s", initial, path);
643 /* XXX: Use system. In fact, use action! */
645 if (system(command->str))
647 g_string_append(command, " failed!");
648 report_error("ROX-Filer", command->str);
649 retval = FALSE;
652 g_string_free(command, TRUE);
654 refresh_dirs(new_dir);
655 return retval;
658 static void copy_item(gpointer data, guint action, GtkWidget *widget)
660 Collection *collection;
662 g_return_if_fail(window_with_focus != NULL);
664 collection = window_with_focus->collection;
665 if (collection->number_selected > 1)
667 report_error("ROX-Filer", "You cannot do this to more than "
668 "one item at a time");
669 return;
671 else if (collection->number_selected != 1)
672 collection_target(collection, target_callback, copy_item);
673 else
675 DirItem *item = selected_item(collection);
677 savebox_show(window_with_focus, "Copy",
678 window_with_focus->path,
679 item->leafname,
680 item->image, copy_cb);
684 static gboolean rename_cb(char *initial, char *path)
686 if (rename(initial, path))
688 report_error("ROX-Filer: rename()", g_strerror(errno));
689 return FALSE;
691 return TRUE;
694 static void rename_item(gpointer data, guint action, GtkWidget *widget)
696 Collection *collection;
698 g_return_if_fail(window_with_focus != NULL);
700 collection = window_with_focus->collection;
701 if (collection->number_selected > 1)
703 report_error("ROX-Filer", "You cannot do this to more than "
704 "one item at a time");
705 return;
707 else if (collection->number_selected != 1)
708 collection_target(collection, target_callback, rename_item);
709 else
711 DirItem *item = selected_item(collection);
713 savebox_show(window_with_focus, "Rename",
714 window_with_focus->path,
715 item->leafname,
716 item->image, rename_cb);
720 static gboolean link_cb(char *initial, char *path)
722 if (symlink(initial, path))
724 report_error("ROX-Filer: symlink()", g_strerror(errno));
725 return FALSE;
727 return TRUE;
730 static void link_item(gpointer data, guint action, GtkWidget *widget)
732 Collection *collection;
734 g_return_if_fail(window_with_focus != NULL);
736 collection = window_with_focus->collection;
737 if (collection->number_selected > 1)
739 report_error("ROX-Filer", "You cannot do this to more than "
740 "one item at a time");
741 return;
743 else if (collection->number_selected != 1)
744 collection_target(collection, target_callback, link_item);
745 else
747 DirItem *item = selected_item(collection);
749 savebox_show(window_with_focus, "Symlink",
750 window_with_focus->path,
751 item->leafname,
752 item->image, link_cb);
756 static void open_file(gpointer data, guint action, GtkWidget *widget)
758 Collection *collection;
760 g_return_if_fail(window_with_focus != NULL);
762 collection = window_with_focus->collection;
763 if (collection->number_selected > 1)
765 report_error("ROX-Filer", "You cannot do this to more than "
766 "one item at a time");
767 return;
769 else if (collection->number_selected != 1)
770 collection_target(collection, target_callback, open_file);
771 else
772 filer_openitem(window_with_focus,
773 selected_item_number(collection),
774 OPEN_SAME_WINDOW | OPEN_SHIFT);
777 /* Got some data from file(1) - stick it in the window. */
778 static void add_file_output(FileStatus *fs,
779 gint source, GdkInputCondition condition)
781 char buffer[20];
782 char *str;
783 int got;
785 got = read(source, buffer, sizeof(buffer) - 1);
786 if (got <= 0)
788 int err = errno;
789 gtk_input_remove(fs->input);
790 close(source);
791 fs->fd = -1;
792 if (got < 0)
793 delayed_error("ROX-Filer: file(1) says...",
794 g_strerror(err));
795 return;
797 buffer[got] = '\0';
799 if (fs->start)
801 str = "";
802 fs->start = FALSE;
804 else
805 gtk_label_get(fs->label, &str);
807 str = g_strconcat(str, buffer, NULL);
808 gtk_label_set_text(fs->label, str);
809 g_free(str);
812 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs)
814 if (fs->fd != -1)
816 gtk_input_remove(fs->input);
817 close(fs->fd);
820 g_free(fs);
823 static void show_file_info(gpointer data, guint action, GtkWidget *widget)
825 GtkWidget *window, *table, *label, *button, *frame;
826 GtkWidget *file_label;
827 GString *gstring;
828 char *string;
829 int file_data[2];
830 char *path;
831 char *argv[] = {"file", "-b", NULL, NULL};
832 Collection *collection;
833 DirItem *file;
834 struct stat info;
835 FileStatus *fs = NULL;
836 guint perm;
838 g_return_if_fail(window_with_focus != NULL);
840 collection = window_with_focus->collection;
841 if (collection->number_selected > 1)
843 report_error("ROX-Filer", "You cannot do this to more than "
844 "one item at a time");
845 return;
847 else if (collection->number_selected != 1)
849 collection_target(collection, target_callback, show_file_info);
850 return;
852 file = selected_item(collection);
853 path = make_path(window_with_focus->path,
854 file->leafname)->str;
855 if (lstat(path, &info))
857 delayed_error("ROX-Filer", g_strerror(errno));
858 return;
861 gstring = g_string_new(NULL);
863 window = gtk_window_new(GTK_WINDOW_DIALOG);
864 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_MOUSE);
865 gtk_container_set_border_width(GTK_CONTAINER(window), 4);
866 gtk_window_set_title(GTK_WINDOW(window), path);
868 table = gtk_table_new(9, 2, FALSE);
869 gtk_container_add(GTK_CONTAINER(window), table);
870 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
871 gtk_table_set_col_spacings(GTK_TABLE(table), 4);
873 label = gtk_label_new("Owner, group:");
874 gtk_misc_set_alignment(GTK_MISC(label), 1, .5);
875 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_RIGHT);
876 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
877 g_string_sprintf(gstring, "%s, %s", user_name(info.st_uid),
878 group_name(info.st_gid));
879 label = gtk_label_new(gstring->str);
880 gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
881 gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 0, 1);
883 label = gtk_label_new("Size:");
884 gtk_misc_set_alignment(GTK_MISC(label), 1, .5);
885 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
886 if (info.st_size >= PRETTY_SIZE_LIMIT)
888 g_string_sprintf(gstring, "%s (%ld bytes)",
889 format_size((unsigned long) info.st_size),
890 (unsigned long) info.st_size);
892 else
894 g_string_sprintf(gstring, "%ld bytes",
895 (unsigned long) info.st_size);
897 label = gtk_label_new(gstring->str);
898 gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
899 gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 1, 2);
901 label = gtk_label_new("Change time:");
902 gtk_misc_set_alignment(GTK_MISC(label), 1, .5);
903 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
904 label = gtk_label_new(pretty_time(&info.st_ctime));
905 gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
906 gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 2, 3);
908 label = gtk_label_new("Modify time:");
909 gtk_misc_set_alignment(GTK_MISC(label), 1, .5);
910 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4);
911 label = gtk_label_new(pretty_time(&info.st_mtime));
912 gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
913 gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 3, 4);
915 label = gtk_label_new("Access time:");
916 gtk_misc_set_alignment(GTK_MISC(label), 1, .5);
917 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 4, 5);
918 label = gtk_label_new(pretty_time(&info.st_atime));
919 gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
920 gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 4, 5);
922 label = gtk_label_new("Permissions:");
923 gtk_misc_set_alignment(GTK_MISC(label), 1, .5);
924 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 5, 6);
925 label = gtk_label_new(pretty_permissions(info.st_mode));
926 perm = applicable(info.st_uid, info.st_gid);
927 gtk_label_set_pattern(GTK_LABEL(label),
928 perm == 0 ? "___ " :
929 perm == 1 ? " ___ " :
930 " ___");
931 gtk_widget_set_style(label, fixed_style);
932 gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
933 gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 5, 6);
935 label = gtk_label_new("MIME type:");
936 gtk_misc_set_alignment(GTK_MISC(label), 1, .5);
937 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 6, 7);
938 if (file->mime_type)
940 string = g_strconcat(file->mime_type->media_type, "/",
941 file->mime_type->subtype, NULL);
942 label = gtk_label_new(string);
943 g_free(string);
945 else
946 label = gtk_label_new("-");
947 gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
948 gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 6, 7);
950 frame = gtk_frame_new("file(1) says...");
951 gtk_table_attach_defaults(GTK_TABLE(table), frame, 0, 2, 7, 8);
952 file_label = gtk_label_new("<nothing yet>");
953 gtk_misc_set_padding(GTK_MISC(file_label), 4, 4);
954 gtk_label_set_line_wrap(GTK_LABEL(file_label), TRUE);
955 gtk_container_add(GTK_CONTAINER(frame), file_label);
957 button = gtk_button_new_with_label("OK");
958 gtk_window_set_focus(GTK_WINDOW(window), button);
959 gtk_table_attach(GTK_TABLE(table), button, 0, 2, 8, 9,
960 GTK_EXPAND | GTK_FILL | GTK_SHRINK, 0, 40, 4);
961 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
962 gtk_widget_destroy, GTK_OBJECT(window));
964 gtk_widget_show_all(window);
965 gdk_flush();
967 if (pipe(file_data))
969 g_string_sprintf(gstring, "pipe(): %s", g_strerror(errno));
970 g_string_free(gstring, TRUE);
971 return;
973 switch (fork())
975 case -1:
976 g_string_sprintf(gstring, "fork(): %s",
977 g_strerror(errno));
978 gtk_label_set_text(GTK_LABEL(file_label), gstring->str);
979 g_string_free(gstring, TRUE);
980 close(file_data[0]);
981 close(file_data[1]);
982 break;
983 case 0:
984 /* We are the child */
985 close(file_data[0]);
986 dup2(file_data[1], STDOUT_FILENO);
987 dup2(file_data[1], STDERR_FILENO);
988 #ifdef FILE_B_FLAG
989 argv[2] = path;
990 #else
991 argv[1] = file->leafname;
992 chdir(window_with_focus->path);
993 #endif
994 if (execvp(argv[0], argv))
995 fprintf(stderr, "execvp() error: %s\n",
996 g_strerror(errno));
997 _exit(0);
998 default:
999 /* We are the parent */
1000 close(file_data[1]);
1001 fs = g_new(FileStatus, 1);
1002 fs->label = GTK_LABEL(file_label);
1003 fs->fd = file_data[0];
1004 fs->start = TRUE;
1005 fs->input = gdk_input_add(fs->fd, GDK_INPUT_READ,
1006 (GdkInputFunction) add_file_output, fs);
1007 gtk_signal_connect(GTK_OBJECT(window), "destroy",
1008 GTK_SIGNAL_FUNC(file_info_destroyed), fs);
1009 g_string_free(gstring, TRUE);
1010 break;
1014 static void app_show_help(char *path)
1016 char *help_dir;
1017 struct stat info;
1019 help_dir = g_strconcat(path, "/Help", NULL);
1021 if (stat(help_dir, &info))
1022 delayed_error("Application",
1023 "This is an application directory - you can "
1024 "run it as a program, or open it (hold down "
1025 "Shift while you open it). Most applications provide "
1026 "their own help here, but this one doesn't.");
1027 else
1028 filer_opendir(help_dir, PANEL_NO);
1031 static void help(gpointer data, guint action, GtkWidget *widget)
1033 Collection *collection;
1034 DirItem *item;
1036 g_return_if_fail(window_with_focus != NULL);
1038 collection = window_with_focus->collection;
1039 if (collection->number_selected > 1)
1041 report_error("ROX-Filer", "You cannot do this to more than "
1042 "one item at a time");
1043 return;
1045 else if (collection->number_selected != 1)
1047 collection_target(collection, target_callback, help);
1048 return;
1050 item = selected_item(collection);
1051 switch (item->base_type)
1053 case TYPE_FILE:
1054 if (item->flags & ITEM_FLAG_EXEC_FILE)
1055 delayed_error("Executable file",
1056 "This is a file with an eXecute bit "
1057 "set - it can be run as a program.");
1058 else
1059 delayed_error("File",
1060 "This is a data file. Try using the "
1061 "Info menu item to find out more...");
1062 break;
1063 case TYPE_DIRECTORY:
1064 if (item->flags & ITEM_FLAG_APPDIR)
1065 app_show_help(
1066 make_path(window_with_focus->path,
1067 item->leafname)->str);
1068 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
1069 delayed_error("Mount point",
1070 "A mount point is a directory which another "
1071 "filing system can be mounted on. Everything "
1072 "on the mounted filesystem then appears to be "
1073 "inside the directory.");
1074 else
1075 delayed_error("Directory",
1076 "This is a directory. It contains an index to "
1077 "other items - open it to see the list.");
1078 break;
1079 case TYPE_CHAR_DEVICE:
1080 case TYPE_BLOCK_DEVICE:
1081 delayed_error("Device file",
1082 "Device files allow you to read from or write "
1083 "to a device driver as though it was an "
1084 "ordinary file.");
1085 break;
1086 case TYPE_PIPE:
1087 delayed_error("Named pipe",
1088 "Pipes allow different programs to "
1089 "communicate. One program writes data to the "
1090 "pipe while another one reads it out again.");
1091 break;
1092 case TYPE_SOCKET:
1093 delayed_error("Socket",
1094 "Sockets allow processes to communicate.");
1095 break;
1096 default:
1097 delayed_error("Unknown type",
1098 "I couldn't find out what kind of file this "
1099 "is. Maybe it doesn't exist anymore or you "
1100 "don't have search permission on the directory "
1101 "it's in?");
1102 break;
1106 static void select_all(gpointer data, guint action, GtkWidget *widget)
1108 g_return_if_fail(window_with_focus != NULL);
1110 collection_select_all(window_with_focus->collection);
1111 window_with_focus->temp_item_selected = FALSE;
1114 static void clear_selection(gpointer data, guint action, GtkWidget *widget)
1116 g_return_if_fail(window_with_focus != NULL);
1118 collection_clear_selection(window_with_focus->collection);
1119 window_with_focus->temp_item_selected = FALSE;
1122 static void show_options(gpointer data, guint action, GtkWidget *widget)
1124 g_return_if_fail(window_with_focus != NULL);
1126 options_show(window_with_focus);
1129 static gboolean new_directory_cb(char *initial, char *path)
1131 if (mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO))
1133 report_error("mkdir", g_strerror(errno));
1134 return FALSE;
1136 return TRUE;
1139 static void new_directory(gpointer data, guint action, GtkWidget *widget)
1141 g_return_if_fail(window_with_focus != NULL);
1143 savebox_show(window_with_focus, "Create directory",
1144 window_with_focus->path, "NewDir",
1145 default_pixmap + TYPE_DIRECTORY, new_directory_cb);
1148 static void xterm_here(gpointer data, guint action, GtkWidget *widget)
1150 char *argv[] = {NULL, NULL};
1152 argv[0] = xterm_here_value;
1154 g_return_if_fail(window_with_focus != NULL);
1156 if (!spawn_full(argv, window_with_focus->path))
1157 report_error("ROX-Filer", "Failed to fork() child "
1158 "process");
1161 static void open_parent(gpointer data, guint action, GtkWidget *widget)
1163 char *copy;
1164 char *slash;
1166 g_return_if_fail(window_with_focus != NULL);
1168 if (window_with_focus->path[0] == '/'
1169 && window_with_focus->path[1] == '\0')
1170 return; /* Already in the root */
1172 copy = g_strdup(window_with_focus->path);
1173 slash = strrchr(copy, '/');
1175 if (slash)
1177 *slash = '\0';
1178 filer_opendir(*copy ? copy : "/", PANEL_NO);
1180 else
1181 g_warning("No / in directory path!\n");
1183 g_free(copy);
1186 static void open_parent_same(gpointer data, guint action, GtkWidget *widget)
1188 g_return_if_fail(window_with_focus != NULL);
1190 change_to_parent(window_with_focus);
1193 static void new_window(gpointer data, guint action, GtkWidget *widget)
1195 g_return_if_fail(window_with_focus != NULL);
1197 filer_opendir(window_with_focus->path, PANEL_NO);
1200 static void close_window(gpointer data, guint action, GtkWidget *widget)
1202 g_return_if_fail(window_with_focus != NULL);
1204 gtk_widget_destroy(window_with_focus->window);
1207 static void enter_path(gpointer data, guint action, GtkWidget *widget)
1209 g_return_if_fail(window_with_focus != NULL);
1211 minibuffer_show(window_with_focus);
1214 static void rox_help(gpointer data, guint action, GtkWidget *widget)
1216 g_return_if_fail(window_with_focus != NULL);
1218 filer_opendir(make_path(getenv("APP_DIR"), "Help")->str, PANEL_NO);
1221 static void open_as_dir(gpointer data, guint action, GtkWidget *widget)
1223 g_return_if_fail(window_with_focus != NULL);
1225 filer_opendir(window_with_focus->path, PANEL_NO);
1228 static void close_panel(gpointer data, guint action, GtkWidget *widget)
1230 g_return_if_fail(window_with_focus != NULL);
1232 gtk_widget_destroy(window_with_focus->window);