r1774: Updated Spanish translation (Marcelo Ramos).
[rox-filer.git] / ROX-Filer / src / infobox.c
bloba34f774dc9fc28a336e802eb74c2aa80715a530c
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, the ROX-Filer team.
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 /* infobox.c - code for showing a file's attributes */
24 #include "config.h"
26 #include <errno.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <sys/param.h>
30 #include <libxml/parser.h>
32 #include <gtk/gtk.h>
34 #include "global.h"
36 #include "support.h"
37 #include "main.h"
38 #include "gui_support.h"
39 #include "diritem.h"
40 #include "type.h"
41 #include "infobox.h"
42 #include "appinfo.h"
43 #include "dnd.h" /* For xa_string */
44 #include "run.h" /* For show_item_help() */
45 #include "xml.h"
46 #include "mount.h"
48 typedef struct _FileStatus FileStatus;
50 /* This is for the 'file(1) says...' thing */
51 struct _FileStatus
53 int fd; /* FD to read from, -1 if closed */
54 int input; /* Input watcher tag if fd valid */
55 GtkLabel *label; /* Widget to output to */
56 gchar *text; /* String so far */
59 /* Static prototypes */
60 static void refresh_info(GObject *window);
61 static GtkWidget *make_vbox(guchar *path);
62 static GtkWidget *make_details(guchar *path, DirItem *item);
63 static GtkWidget *make_about(guchar *path, xmlNode *about);
64 static GtkWidget *make_file_says(guchar *path);
65 static void add_file_output(FileStatus *fs,
66 gint source, GdkInputCondition condition);
67 static guchar *pretty_type(DirItem *file, guchar *path);
68 static void got_response(GObject *window, gint response, gpointer data);
69 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs);
71 /****************************************************************
72 * EXTERNAL INTERFACE *
73 ****************************************************************/
75 /* Open each item in a new infobox. Confirms if there are a large
76 * number of items to show.
78 void infobox_show_list(GList *paths)
80 int n;
82 n = g_list_length(paths);
84 if (n >= 10)
86 gchar *message;
87 int button;
89 message = g_strdup_printf(
90 _("Are you sure you want to open %d windows?"), n);
91 button = get_choice(_("File Information"),
92 message, 2, _("Cancel"), _("Show Info"));
93 g_free(message);
94 if (button != 1)
95 return;
98 g_list_foreach(paths, (GFunc) infobox_new, NULL);
101 /* Create and display a new info box showing details about this item */
102 void infobox_new(const gchar *pathname)
104 GtkWidget *window, *details;
105 gchar *path;
107 g_return_if_fail(pathname != NULL);
109 path = g_strdup(pathname); /* Gets attached to window & freed later */
111 window = gtk_dialog_new_with_buttons(path,
112 NULL, GTK_DIALOG_NO_SEPARATOR,
113 GTK_STOCK_CANCEL, GTK_RESPONSE_DELETE_EVENT,
114 GTK_STOCK_REFRESH, GTK_RESPONSE_APPLY,
115 NULL);
117 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_MOUSE);
119 details = make_vbox(path);
120 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox),
121 details, TRUE, TRUE, 0);
123 g_object_set_data(G_OBJECT(window), "details", details);
124 g_object_set_data_full(G_OBJECT(window), "path", path, g_free);
126 g_signal_connect(window, "response", G_CALLBACK(got_response), NULL);
128 number_of_windows++;
129 gtk_widget_show_all(window);
132 /****************************************************************
133 * INTERNAL FUNCTIONS *
134 ****************************************************************/
136 static void show_help_clicked(const gchar *path)
138 DirItem *item;
140 item = diritem_new("");
141 diritem_restat(path, item, NULL);
142 show_item_help(path, item);
143 diritem_free(item);
146 static void got_response(GObject *window, gint response, gpointer data)
148 if (response == GTK_RESPONSE_APPLY)
149 refresh_info(window);
150 else
152 gtk_widget_destroy(GTK_WIDGET(window));
153 one_less_window();
157 static void refresh_info(GObject *window)
159 GtkWidget *details, *vbox;
160 guchar *path;
162 path = g_object_get_data(window, "path");
163 details = g_object_get_data(window, "details");
164 g_return_if_fail(details != NULL);
165 g_return_if_fail(path != NULL);
167 vbox = details->parent;
168 gtk_widget_destroy(details);
170 details = make_vbox(path);
171 g_object_set_data(window, "details", details);
172 gtk_box_pack_start(GTK_BOX(vbox), details, TRUE, TRUE, 0);
173 gtk_widget_show_all(details);
176 /* Create the VBox widget that contains the details.
177 * Note that 'path' must not be freed until the vbox is destroyed.
179 static GtkWidget *make_vbox(guchar *path)
181 DirItem *item;
182 GtkWidget *vbox, *list, *frame;
183 XMLwrapper *ai;
184 xmlNode *about = NULL;
185 gchar *help_dir;
187 g_return_val_if_fail(path[0] == '/', NULL);
189 item = diritem_new(g_basename(path));
190 diritem_restat(path, item, NULL);
192 vbox = gtk_vbox_new(FALSE, 4);
194 ai = appinfo_get(path, item);
195 if (ai)
196 about = xml_get_section(ai, NULL, "About");
198 frame = gtk_frame_new(NULL);
199 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
200 list = make_details(path, item);
201 gtk_container_add(GTK_CONTAINER(frame), list);
202 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
204 help_dir = g_strconcat(path, "/Help", NULL);
205 if (access(help_dir, F_OK) == 0)
207 GtkWidget *button, *align;
209 align = gtk_alignment_new(0.5, 0.5, 0, 0);
211 button = button_new_mixed(GTK_STOCK_JUMP_TO,
212 _("Show _Help Files"));
213 gtk_box_pack_start(GTK_BOX(vbox), align, FALSE, TRUE, 0);
214 gtk_container_add(GTK_CONTAINER(align), button);
215 g_signal_connect_swapped(button, "clicked",
216 G_CALLBACK(show_help_clicked),
217 path);
219 g_free(help_dir);
221 if (about)
223 frame = gtk_frame_new(NULL);
224 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
225 list = make_about(path, about);
226 gtk_container_add(GTK_CONTAINER(frame), list);
227 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
229 else
230 gtk_box_pack_start(GTK_BOX(vbox), make_file_says(path), TRUE,
231 TRUE, 0);
233 if (ai)
234 g_object_unref(ai);
236 diritem_free(item);
238 return vbox;
241 /* The selection has changed - grab or release the primary selection */
242 static void set_selection(GtkTreeView *view, gpointer data)
244 static GtkClipboard *primary = NULL;
245 GtkTreeModel *model;
246 GtkTreePath *path = NULL;
247 GtkTreeIter iter;
248 gchar *text;
250 gtk_tree_view_get_cursor(view, &path, NULL);
251 if (!path)
252 return;
254 if (!primary)
255 primary = gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE));
257 model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
259 gtk_tree_model_get_iter(model, &iter, path);
260 gtk_tree_path_free(path);
262 gtk_tree_model_get(model, &iter, 1, &text, -1);
264 gtk_clipboard_set_text(primary, text, -1);
266 g_free(text);
269 static void add_row(GtkListStore *store, const gchar *label, const gchar *data)
271 GtkTreeIter iter;
273 gtk_list_store_append(store, &iter);
274 gtk_list_store_set(store, &iter, 0, label, 1, data, -1);
277 /* Create an empty list view, ready to place some data in */
278 static void make_list(GtkListStore **list_store, GtkWidget **list_view)
280 GtkListStore *store;
281 GtkWidget *view;
282 GtkCellRenderer *cell_renderer;
284 store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
285 view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
286 g_object_unref(G_OBJECT(store));
287 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
289 cell_renderer = gtk_cell_renderer_text_new();
290 g_object_set(G_OBJECT(cell_renderer), "xalign", 1.0, NULL);
291 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
292 0, NULL, cell_renderer, "text", 0, NULL);
294 cell_renderer = gtk_cell_renderer_text_new();
295 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
296 1, NULL, cell_renderer, "text", 1, NULL);
298 g_signal_connect(view, "cursor_changed",
299 G_CALLBACK(set_selection), NULL);
301 *list_store = store;
302 *list_view = view;
305 /* Create the TreeView widget with the file's details */
306 static GtkWidget *make_details(guchar *path, DirItem *item)
308 GtkListStore *store;
309 GtkWidget *view;
310 GString *gstring;
311 struct stat info;
312 gchar *tmp, *tmp2;
314 make_list(&store, &view);
316 add_row(store, _("Name:"), item->leafname);
318 if (lstat(path, &info))
320 add_row(store, _("Error:"), g_strerror(errno));
321 return view;
324 tmp = g_dirname(path);
325 tmp2 = pathdup(tmp);
326 if (strcmp(tmp, tmp2) != 0)
327 add_row(store, _("Real directory:"), tmp2);
328 g_free(tmp);
329 g_free(tmp2);
331 gstring = g_string_new(NULL);
333 g_string_sprintf(gstring, "%s, %s", user_name(info.st_uid),
334 group_name(info.st_gid));
335 add_row(store, _("Owner, Group:"), gstring->str);
337 if (info.st_size >= PRETTY_SIZE_LIMIT)
339 g_string_sprintf(gstring, "%s (%" SIZE_FMT " %s)",
340 format_size(info.st_size),
341 info.st_size, _("bytes"));
343 else
345 g_string_assign(gstring,
346 format_size(info.st_size));
348 add_row(store, _("Size:"), gstring->str);
350 tmp = pretty_time(&info.st_ctime);
351 add_row(store, _("Change time:"), tmp);
352 g_free(tmp);
354 tmp = pretty_time(&info.st_mtime);
355 add_row(store, _("Modify time:"), tmp);
356 g_free(tmp);
358 tmp = pretty_time(&info.st_atime);
359 add_row(store, _("Access time:"), tmp);
360 g_free(tmp);
362 g_string_free(gstring, TRUE);
364 add_row(store, _("Permissions:"), pretty_permissions(info.st_mode));
366 tmp = pretty_type(item, path);
367 add_row(store, _("Type:"), tmp);
368 g_free(tmp);
370 if (item->base_type != TYPE_DIRECTORY)
372 tmp = describe_current_command(item->mime_type);
373 add_row(store, _("Run action:"), tmp);
374 g_free(tmp);
377 return view;
380 /* Create the TreeView widget with the application's details, or
381 * the file(1) summary (for non-apps).
383 static GtkWidget *make_about(guchar *path, xmlNode *about)
385 GtkListStore *store;
386 GtkWidget *view;
387 xmlNode *prop;
388 gchar *tmp;
390 g_return_val_if_fail(about != NULL, NULL);
392 make_list(&store, &view);
394 for (prop = about->xmlChildrenNode; prop; prop = prop->next)
396 if (prop->type == XML_ELEMENT_NODE)
398 char *l;
400 l = g_strconcat((char *) prop->name, ":", NULL);
401 tmp = xmlNodeListGetString(prop->doc,
402 prop->xmlChildrenNode, 1);
403 if (!tmp)
404 tmp = g_strdup("-");
405 add_row(store, l, tmp);
406 g_free(l);
407 g_free(tmp);
411 return view;
414 static GtkWidget *make_file_says(guchar *path)
416 GtkWidget *frame;
417 GtkWidget *file_label;
418 int file_data[2];
419 char *argv[] = {"file", "-b", NULL, NULL};
420 FileStatus *fs = NULL;
421 guchar *tmp;
423 frame = gtk_frame_new(_("file(1) says..."));
424 file_label = gtk_label_new(_("<nothing yet>"));
425 gtk_misc_set_padding(GTK_MISC(file_label), 4, 4);
426 gtk_label_set_line_wrap(GTK_LABEL(file_label), TRUE);
427 gtk_container_add(GTK_CONTAINER(frame), file_label);
429 if (pipe(file_data))
431 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
432 gtk_label_set_text(GTK_LABEL(file_label), tmp);
433 g_free(tmp);
434 return frame;
437 switch (fork())
439 case -1:
440 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
441 gtk_label_set_text(GTK_LABEL(file_label), tmp);
442 g_free(tmp);
443 close(file_data[0]);
444 close(file_data[1]);
445 break;
446 case 0:
447 /* We are the child */
448 close(file_data[0]);
449 dup2(file_data[1], STDOUT_FILENO);
450 dup2(file_data[1], STDERR_FILENO);
451 #ifdef FILE_B_FLAG
452 argv[2] = path;
453 #else
454 argv[1] = (char *) g_basename(path);
455 chdir(g_dirname(path));
456 #endif
457 if (execvp(argv[0], argv))
458 fprintf(stderr, "execvp() error: %s\n",
459 g_strerror(errno));
460 _exit(0);
461 default:
462 /* We are the parent */
463 close(file_data[1]);
464 fs = g_new(FileStatus, 1);
465 fs->label = GTK_LABEL(file_label);
466 fs->fd = file_data[0];
467 fs->text = g_strdup("");
468 fs->input = gtk_input_add_full(fs->fd, GDK_INPUT_READ,
469 (GdkInputFunction) add_file_output,
470 NULL, fs, NULL);
471 g_signal_connect(frame, "destroy",
472 G_CALLBACK(file_info_destroyed), fs);
473 break;
476 return frame;
479 /* Got some data from file(1) - stick it in the window. */
480 static void add_file_output(FileStatus *fs,
481 gint source, GdkInputCondition condition)
483 char buffer[20];
484 char *str;
485 int got;
487 got = read(source, buffer, sizeof(buffer) - 1);
488 if (got <= 0)
490 int err = errno;
491 gtk_input_remove(fs->input);
492 close(source);
493 fs->fd = -1;
494 if (got < 0)
495 delayed_error(_("file(1) says... %s"),
496 g_strerror(err));
497 return;
499 buffer[got] = '\0';
501 str = g_strconcat(fs->text, buffer, NULL);
502 g_free(fs->text);
503 fs->text = str;
505 str = g_strdup(fs->text);
506 g_strstrip(str);
507 gtk_label_set_text(fs->label, str);
508 g_free(str);
511 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs)
513 if (fs->fd != -1)
515 gtk_input_remove(fs->input);
516 close(fs->fd);
519 g_free(fs->text);
520 g_free(fs);
523 /* g_free() the result */
524 static guchar *pretty_type(DirItem *file, guchar *path)
526 if (file->flags & ITEM_FLAG_SYMLINK)
528 char *target;
530 target = readlink_dup(path);
531 if (target)
533 char *retval;
535 retval = g_strdup_printf(_("Symbolic link to %s"),
536 target);
537 g_free(target);
538 return retval;
541 return g_strdup(_("Symbolic link"));
544 if (file->flags & ITEM_FLAG_APPDIR)
545 return g_strdup(_("ROX application"));
547 if (file->flags & ITEM_FLAG_MOUNT_POINT)
549 MountPoint *mp;
550 const char *mounted;
552 mounted = mount_is_mounted(path, NULL, NULL)
553 ? _("mounted") : _("unmounted");
555 mp = g_hash_table_lookup(fstab_mounts, path);
556 if (mp)
557 return g_strdup_printf(_("Mount point for %s (%s)"),
558 mp->name, mounted);
559 return g_strdup_printf(_("Mount point (%s)"), mounted);
562 if (file->mime_type)
563 return g_strconcat(file->mime_type->media_type, "/",
564 file->mime_type->subtype, NULL);
566 return g_strdup("-");