r898: Applied Bernard Jungen's latest patch:
[rox-filer.git] / ROX-Filer / src / infobox.c
blob1ff66d153947c6a432ad22e0f7ba378a8d595174
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2001, 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 <parser.h>
32 #include <gtk/gtk.h>
34 #include "global.h"
36 #include "support.h"
37 #include "gui_support.h"
38 #include "dir.h"
39 #include "type.h"
40 #include "infobox.h"
41 #include "appinfo.h"
42 #include "dnd.h" /* For xa_string */
44 typedef struct _FileStatus FileStatus;
46 /* This is for the 'file(1) says...' thing */
47 struct _FileStatus
49 int fd; /* FD to read from, -1 if closed */
50 int input; /* Input watcher tag if fd valid */
51 GtkLabel *label; /* Widget to output to */
52 gboolean start; /* No output yet */
55 /* Static prototypes */
56 static void refresh_info(GtkObject *window);
57 static GtkWidget *make_vbox(guchar *path);
58 static GtkWidget *make_clist(guchar *path, DirItem *item, xmlNode *about);
59 static GtkWidget *make_file_says(guchar *path);
60 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs);
61 static void add_file_output(FileStatus *fs,
62 gint source, GdkInputCondition condition);
63 static guchar *pretty_type(DirItem *file, guchar *path);
64 static GtkWidget *make_vbox(guchar *path);
66 /****************************************************************
67 * EXTERNAL INTERFACE *
68 ****************************************************************/
70 /* Create and display a new info box showing details about this item */
71 void infobox_new(guchar *path)
73 GtkWidget *window, *hbox, *vbox, *details, *button;
75 g_return_if_fail(path != NULL);
77 window = gtk_window_new(GTK_WINDOW_DIALOG);
78 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_MOUSE);
79 gtk_container_set_border_width(GTK_CONTAINER(window), 4);
80 gtk_window_set_title(GTK_WINDOW(window), path);
82 vbox = gtk_vbox_new(FALSE, 4);
83 gtk_container_add(GTK_CONTAINER(window), vbox);
85 details = make_vbox(path);
86 gtk_box_pack_start(GTK_BOX(vbox), details, TRUE, TRUE, 0);
88 hbox = gtk_hbox_new(TRUE, 4);
89 gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
91 button = gtk_button_new_with_label(_("Refresh"));
92 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(button), GTK_CAN_DEFAULT);
93 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
94 GTK_SIGNAL_FUNC(refresh_info),
95 GTK_OBJECT(window));
96 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
98 button = gtk_button_new_with_label(_("Cancel"));
99 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(button), GTK_CAN_DEFAULT);
100 gtk_window_set_default(GTK_WINDOW(window), button);
101 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
102 GTK_SIGNAL_FUNC(gtk_widget_destroy),
103 GTK_OBJECT(window));
104 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
106 gtk_object_set_data(GTK_OBJECT(window), "details", details);
107 gtk_object_set_data_full(GTK_OBJECT(window), "path", g_strdup(path),
108 g_free);
110 gtk_widget_show_all(window);
113 /****************************************************************
114 * INTERNAL FUNCTIONS *
115 ****************************************************************/
117 static void refresh_info(GtkObject *window)
119 GtkWidget *details, *vbox;
120 guchar *path;
122 path = gtk_object_get_data(window, "path");
123 details = gtk_object_get_data(window, "details");
124 g_return_if_fail(details != NULL);
125 g_return_if_fail(path != NULL);
127 vbox = details->parent;
128 gtk_widget_destroy(details);
130 details = make_vbox(path);
131 gtk_object_set_data(window, "details", details);
132 gtk_box_pack_start(GTK_BOX(vbox), details, TRUE, TRUE, 0);
133 gtk_widget_show_all(details);
136 /* Create the VBox widget that contains the details */
137 static GtkWidget *make_vbox(guchar *path)
139 DirItem item;
140 GtkWidget *vbox, *list, *file;
141 AppInfo *ai;
142 xmlNode *about = NULL;
144 g_return_val_if_fail(path[0] == '/', NULL);
146 diritem_stat(path, &item, FALSE);
147 item.leafname = strrchr(path, '/') + 1;
149 vbox = gtk_vbox_new(FALSE, 4);
151 ai = appinfo_get(path, &item);
152 if (ai)
153 about = appinfo_get_section(ai, "About");
155 list = make_clist(path, &item, about);
156 gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0);
158 if (!about)
160 file = make_file_says(path);
161 gtk_box_pack_start(GTK_BOX(vbox), file, TRUE, TRUE, 0);
164 if (ai)
165 appinfo_unref(ai);
167 item.leafname = NULL;
168 diritem_clear(&item);
170 return vbox;
173 static guchar *selection_text = NULL;
175 /* The selection has changed - grab or release the primary selection */
176 static void set_selection(GtkCList *clist, int row, int col,
177 GdkEventButton *event, gpointer data)
179 /* If we lose the selection when a row is unselected, there's a race
180 * and it doesn't work - therefore, keep the selection...
182 if (clist->selection)
184 gchar *text;
186 g_return_if_fail(gtk_clist_get_text(clist, row, 1, &text) == 1);
188 gtk_selection_owner_set(GTK_WIDGET(clist),
189 GDK_SELECTION_PRIMARY,
190 event->time);
191 g_free(selection_text);
192 selection_text = g_strdup(text);
196 static void get_selection(GtkCList *clist,
197 GtkSelectionData *selection_data,
198 guint info,
199 guint time,
200 gpointer data)
202 g_return_if_fail(selection_text != NULL);
204 gtk_selection_data_set(selection_data, xa_string,
205 8, selection_text, strlen(selection_text));
208 /* Create the CList with the file's details */
209 static GtkWidget *make_clist(guchar *path, DirItem *item, xmlNode *about)
211 GtkCList *table;
212 GString *gstring;
213 struct stat info;
214 char *data[] = {NULL, NULL, NULL};
215 xmlNode *prop;
216 GtkTargetEntry target_table[] = { {"STRING", 0, 0} };
218 table = GTK_CLIST(gtk_clist_new(2));
219 GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(table), GTK_CAN_FOCUS);
220 gtk_clist_set_column_auto_resize(table, 0, TRUE);
221 gtk_clist_set_column_auto_resize(table, 1, TRUE);
222 gtk_clist_set_column_justification(table, 0, GTK_JUSTIFY_RIGHT);
224 gtk_signal_connect(GTK_OBJECT(table), "select-row",
225 GTK_SIGNAL_FUNC(set_selection), NULL);
226 gtk_signal_connect_object(GTK_OBJECT(table), "selection-clear-event",
227 GTK_SIGNAL_FUNC(gtk_clist_unselect_all),
228 GTK_OBJECT(table));
229 gtk_signal_connect(GTK_OBJECT(table), "selection_get",
230 GTK_SIGNAL_FUNC(get_selection), NULL);
231 gtk_selection_add_targets(GTK_WIDGET(table), GDK_SELECTION_PRIMARY,
232 target_table,
233 sizeof(target_table) / sizeof(*target_table));
235 data[0] = _("Name:");
236 data[1] = item->leafname;
237 gtk_clist_append(table, data);
239 if (lstat(path, &info))
241 data[0] = _("Error:");
242 data[1] = g_strerror(errno);
243 gtk_clist_append(table, data);
244 return GTK_WIDGET(table);
247 gstring = g_string_new(NULL);
249 g_string_sprintf(gstring, "%s, %s", user_name(info.st_uid),
250 group_name(info.st_gid));
251 data[0] = _("Owner, Group:");
252 data[1] = gstring->str;
253 gtk_clist_append(table, data);
255 if (info.st_size >= PRETTY_SIZE_LIMIT)
257 g_string_sprintf(gstring, "%s (%ld %s)",
258 format_size((unsigned long) info.st_size),
259 (unsigned long) info.st_size, _("bytes"));
261 else
263 g_string_assign(gstring,
264 format_size((unsigned long) info.st_size));
266 data[0] = _("Size:");
267 data[1] = gstring->str;
268 gtk_clist_append(table, data);
270 data[0] = _("Change time:");
271 data[1] = pretty_time(&info.st_ctime);
272 gtk_clist_append(table, data);
274 data[0] = _("Modify time:");
275 data[1] = pretty_time(&info.st_mtime);
276 gtk_clist_append(table, data);
278 data[0] = _("Access time:");
279 data[1] = pretty_time(&info.st_atime);
280 gtk_clist_append(table, data);
282 g_string_free(gstring, TRUE);
284 data[0] = _("Permissions:");
285 data[1] = pretty_permissions(info.st_mode);
286 gtk_clist_append(table, data);
288 data[0] = _("Type:");
289 data[1] = pretty_type(item, path);
290 gtk_clist_append(table, data);
291 g_free(data[1]);
293 if (about)
295 data[0] = data[1] = "";
296 gtk_clist_append(table, data);
297 gtk_clist_set_selectable(table, table->rows - 1, FALSE);
298 for (prop = about->xmlChildrenNode; prop; prop = prop->next)
300 if (prop->type == XML_ELEMENT_NODE)
302 data[0] = g_strconcat((char *) prop->name,
303 ":", NULL);
304 data[1] = xmlNodeListGetString(prop->doc,
305 prop->xmlChildrenNode, 1);
306 gtk_clist_append(table, data);
307 g_free(data[0]);
308 g_free(data[1]);
313 return GTK_WIDGET(table);
316 static GtkWidget *make_file_says(guchar *path)
318 GtkWidget *frame;
319 GtkWidget *file_label;
320 int file_data[2];
321 char *argv[] = {"file", "-b", NULL, NULL};
322 FileStatus *fs = NULL;
323 guchar *tmp;
325 frame = gtk_frame_new(_("file(1) says..."));
326 file_label = gtk_label_new(_("<nothing yet>"));
327 gtk_misc_set_padding(GTK_MISC(file_label), 4, 4);
328 gtk_label_set_line_wrap(GTK_LABEL(file_label), TRUE);
329 gtk_container_add(GTK_CONTAINER(frame), file_label);
331 if (pipe(file_data))
333 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
334 gtk_label_set_text(GTK_LABEL(file_label), tmp);
335 g_free(tmp);
336 return frame;
339 switch (fork())
341 case -1:
342 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
343 gtk_label_set_text(GTK_LABEL(file_label), tmp);
344 g_free(tmp);
345 close(file_data[0]);
346 close(file_data[1]);
347 break;
348 case 0:
349 /* We are the child */
350 close(file_data[0]);
351 dup2(file_data[1], STDOUT_FILENO);
352 dup2(file_data[1], STDERR_FILENO);
353 #ifdef FILE_B_FLAG
354 argv[2] = path;
355 #else
356 tmp = strrchr(path, '/');
357 argv[1] = tmp + 1;
358 if (tmp > path)
359 chdir(g_strndup(path, tmp - path));
360 else
361 chdir("/");
362 #endif
363 if (execvp(argv[0], argv))
364 fprintf(stderr, "execvp() error: %s\n",
365 g_strerror(errno));
366 _exit(0);
367 default:
368 /* We are the parent */
369 close(file_data[1]);
370 fs = g_new(FileStatus, 1);
371 fs->label = GTK_LABEL(file_label);
372 fs->fd = file_data[0];
373 fs->start = TRUE;
374 fs->input = gdk_input_add(fs->fd, GDK_INPUT_READ,
375 (GdkInputFunction) add_file_output, fs);
376 gtk_signal_connect(GTK_OBJECT(frame), "destroy",
377 GTK_SIGNAL_FUNC(file_info_destroyed), fs);
378 break;
381 return frame;
384 /* Got some data from file(1) - stick it in the window. */
385 static void add_file_output(FileStatus *fs,
386 gint source, GdkInputCondition condition)
388 char buffer[20];
389 char *str;
390 int got;
392 got = read(source, buffer, sizeof(buffer) - 1);
393 if (got <= 0)
395 int err = errno;
396 gtk_input_remove(fs->input);
397 close(source);
398 fs->fd = -1;
399 if (got < 0)
400 delayed_rox_error(_("file(1) says... %s"),
401 g_strerror(err));
402 return;
404 buffer[got] = '\0';
406 if (fs->start)
408 str = "";
409 fs->start = FALSE;
411 else
412 gtk_label_get(fs->label, &str);
414 str = g_strconcat(str, buffer, NULL);
415 gtk_label_set_text(fs->label, str);
416 g_free(str);
419 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs)
421 if (fs->fd != -1)
423 gtk_input_remove(fs->input);
424 close(fs->fd);
427 g_free(fs);
430 /* g_free() the result */
431 static guchar *pretty_type(DirItem *file, guchar *path)
433 if (file->flags & ITEM_FLAG_SYMLINK)
435 char p[MAXPATHLEN + 1];
436 int got;
437 got = readlink(path, p, MAXPATHLEN);
438 if (got > 0 && got <= MAXPATHLEN)
440 p[got] = '\0';
441 return g_strconcat(_("Symbolic link to "), p, NULL);
444 return g_strdup(_("Symbolic link"));
447 if (file->flags & ITEM_FLAG_APPDIR)
448 return g_strdup(_("ROX application"));
450 if (file->flags & ITEM_FLAG_MOUNT_POINT)
451 return g_strdup(_("Mount point"));
453 if (file->mime_type)
454 return g_strconcat(file->mime_type->media_type, "/",
455 file->mime_type->subtype, NULL);
457 return g_strdup("-");