r1283: Added a whole load of 'const' modifiers so we can do extra checking...
[rox-filer.git] / ROX-Filer / src / infobox.c
blob9114d2a2d520df626965e6b5177bc7893a32cd6e
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 */
45 typedef struct _FileStatus FileStatus;
47 /* This is for the 'file(1) says...' thing */
48 struct _FileStatus
50 int fd; /* FD to read from, -1 if closed */
51 int input; /* Input watcher tag if fd valid */
52 GtkLabel *label; /* Widget to output to */
53 gchar *text; /* String so far */
56 /* Static prototypes */
57 static void refresh_info(GtkObject *window);
58 static GtkWidget *make_vbox(guchar *path);
59 static GtkWidget *make_clist(guchar *path, DirItem *item, xmlNode *about);
60 static GtkWidget *make_file_says(guchar *path);
61 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs);
62 static void add_file_output(FileStatus *fs,
63 gint source, GdkInputCondition condition);
64 static guchar *pretty_type(DirItem *file, guchar *path);
65 static GtkWidget *make_vbox(guchar *path);
66 static void info_destroyed(gpointer data);
68 /****************************************************************
69 * EXTERNAL INTERFACE *
70 ****************************************************************/
72 /* Create and display a new info box showing details about this item */
73 void infobox_new(const gchar *pathname)
75 GtkWidget *window, *hbox, *vbox, *details, *button;
76 gchar *path;
78 g_return_if_fail(pathname != NULL);
80 path = g_strdup(pathname); /* Gets attached to window & freed later */
82 window = gtk_window_new(GTK_WINDOW_DIALOG);
83 #ifdef GTK2
84 gtk_window_set_type_hint(GTK_WINDOW(window),
85 GDK_WINDOW_TYPE_HINT_DIALOG);
86 #endif
87 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_MOUSE);
88 gtk_container_set_border_width(GTK_CONTAINER(window), 4);
89 gtk_window_set_title(GTK_WINDOW(window), path);
91 vbox = gtk_vbox_new(FALSE, 4);
92 gtk_container_add(GTK_CONTAINER(window), vbox);
94 details = make_vbox(path);
95 gtk_box_pack_start(GTK_BOX(vbox), details, TRUE, TRUE, 0);
97 hbox = gtk_hbox_new(TRUE, 4);
98 gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
100 button = gtk_button_new_with_label(_("Refresh"));
101 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(button), GTK_CAN_DEFAULT);
102 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
103 GTK_SIGNAL_FUNC(refresh_info),
104 GTK_OBJECT(window));
105 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
107 button = gtk_button_new_with_label(_("Cancel"));
108 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(button), GTK_CAN_DEFAULT);
109 gtk_window_set_default(GTK_WINDOW(window), button);
110 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
111 GTK_SIGNAL_FUNC(gtk_widget_destroy),
112 GTK_OBJECT(window));
113 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
115 gtk_object_set_data(GTK_OBJECT(window), "details", details);
116 gtk_object_set_data_full(GTK_OBJECT(window), "path", path, g_free);
118 gtk_signal_connect_object(GTK_OBJECT(window), "destroy",
119 GTK_SIGNAL_FUNC(info_destroyed), NULL);
121 number_of_windows++;
122 gtk_widget_show_all(window);
125 /****************************************************************
126 * INTERNAL FUNCTIONS *
127 ****************************************************************/
129 static void info_destroyed(gpointer data)
131 if (--number_of_windows < 1)
132 gtk_main_quit();
135 static void refresh_info(GtkObject *window)
137 GtkWidget *details, *vbox;
138 guchar *path;
140 path = gtk_object_get_data(window, "path");
141 details = gtk_object_get_data(window, "details");
142 g_return_if_fail(details != NULL);
143 g_return_if_fail(path != NULL);
145 vbox = details->parent;
146 gtk_widget_destroy(details);
148 details = make_vbox(path);
149 gtk_object_set_data(window, "details", details);
150 gtk_box_pack_start(GTK_BOX(vbox), details, TRUE, TRUE, 0);
151 gtk_widget_show_all(details);
154 /* Create the VBox widget that contains the details */
155 static GtkWidget *make_vbox(guchar *path)
157 DirItem *item;
158 GtkWidget *vbox, *list, *file;
159 XMLwrapper *ai;
160 xmlNode *about = NULL;
162 g_return_val_if_fail(path[0] == '/', NULL);
164 item = diritem_new(NULL);
165 diritem_restat(path, item);
166 item->leafname = strrchr(path, '/') + 1;
168 vbox = gtk_vbox_new(FALSE, 4);
170 ai = appinfo_get(path, item);
171 if (ai)
172 about = appinfo_get_section(ai, "About");
174 list = make_clist(path, item, about);
175 gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0);
177 if (!about)
179 file = make_file_says(path);
180 gtk_box_pack_start(GTK_BOX(vbox), file, TRUE, TRUE, 0);
183 if (ai)
184 xml_cache_unref(ai);
186 item->leafname = NULL;
187 diritem_free(item);
189 return vbox;
192 static guchar *selection_text = NULL;
194 /* The selection has changed - grab or release the primary selection */
195 static void set_selection(GtkCList *clist, int row, int col,
196 GdkEventButton *event, gpointer data)
198 /* If we lose the selection when a row is unselected, there's a race
199 * and it doesn't work - therefore, keep the selection...
201 if (clist->selection)
203 gchar *text;
205 g_return_if_fail(gtk_clist_get_text(clist, row, 1, &text) == 1);
207 gtk_selection_owner_set(GTK_WIDGET(clist),
208 GDK_SELECTION_PRIMARY,
209 event->time);
210 g_free(selection_text);
211 selection_text = g_strdup(text);
215 static void get_selection(GtkCList *clist,
216 GtkSelectionData *selection_data,
217 guint info,
218 guint time,
219 gpointer data)
221 g_return_if_fail(selection_text != NULL);
223 gtk_selection_data_set(selection_data, xa_string,
224 8, selection_text, strlen(selection_text));
227 /* Create the CList with the file's details */
228 static GtkWidget *make_clist(guchar *path, DirItem *item, xmlNode *about)
230 GtkCList *table;
231 GString *gstring;
232 struct stat info;
233 char *data[] = {NULL, NULL, NULL};
234 xmlNode *prop;
235 GtkTargetEntry target_table[] = {
236 {"STRING", 0, 0},
237 {"COMPOUND_TEXT", 0, 0}, /* XXX: Treats as STRING */
240 table = GTK_CLIST(gtk_clist_new(2));
241 GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(table), GTK_CAN_FOCUS);
242 gtk_clist_set_column_auto_resize(table, 0, TRUE);
243 gtk_clist_set_column_auto_resize(table, 1, TRUE);
244 gtk_clist_set_column_justification(table, 0, GTK_JUSTIFY_RIGHT);
246 gtk_signal_connect(GTK_OBJECT(table), "select-row",
247 GTK_SIGNAL_FUNC(set_selection), NULL);
248 gtk_signal_connect_object(GTK_OBJECT(table), "selection-clear-event",
249 GTK_SIGNAL_FUNC(gtk_clist_unselect_all),
250 GTK_OBJECT(table));
251 gtk_signal_connect(GTK_OBJECT(table), "selection_get",
252 GTK_SIGNAL_FUNC(get_selection), NULL);
253 gtk_selection_add_targets(GTK_WIDGET(table), GDK_SELECTION_PRIMARY,
254 target_table,
255 sizeof(target_table) / sizeof(*target_table));
257 data[0] = _("Name:");
258 data[1] = item->leafname;
259 gtk_clist_append(table, data);
261 if (lstat(path, &info))
263 data[0] = _("Error:");
264 data[1] = (guchar *) g_strerror(errno);
265 gtk_clist_append(table, data);
266 return GTK_WIDGET(table);
269 gstring = g_string_new(NULL);
271 g_string_sprintf(gstring, "%s, %s", user_name(info.st_uid),
272 group_name(info.st_gid));
273 data[0] = _("Owner, Group:");
274 data[1] = gstring->str;
275 gtk_clist_append(table, data);
277 if (info.st_size >= PRETTY_SIZE_LIMIT)
279 g_string_sprintf(gstring, "%s (%" SIZE_FMT " %s)",
280 format_size(info.st_size),
281 info.st_size, _("bytes"));
283 else
285 g_string_assign(gstring,
286 format_size(info.st_size));
288 data[0] = _("Size:");
289 data[1] = gstring->str;
290 gtk_clist_append(table, data);
292 data[0] = _("Change time:");
293 data[1] = pretty_time(&info.st_ctime);
294 gtk_clist_append(table, data);
296 data[0] = _("Modify time:");
297 data[1] = pretty_time(&info.st_mtime);
298 gtk_clist_append(table, data);
300 data[0] = _("Access time:");
301 data[1] = pretty_time(&info.st_atime);
302 gtk_clist_append(table, data);
304 g_string_free(gstring, TRUE);
306 data[0] = _("Permissions:");
307 data[1] = pretty_permissions(info.st_mode);
308 gtk_clist_append(table, data);
310 data[0] = _("Type:");
311 data[1] = pretty_type(item, path);
312 gtk_clist_append(table, data);
313 g_free(data[1]);
315 if (item->base_type != TYPE_DIRECTORY)
317 data[0] = _("Run action:");
318 data[1] = describe_current_command(item->mime_type);
319 gtk_clist_append(table, data);
320 g_free(data[1]);
323 if (about)
325 data[0] = data[1] = "";
326 gtk_clist_append(table, data);
327 gtk_clist_set_selectable(table, table->rows - 1, FALSE);
328 for (prop = about->xmlChildrenNode; prop; prop = prop->next)
330 if (prop->type == XML_ELEMENT_NODE)
332 data[0] = g_strconcat((char *) prop->name,
333 ":", NULL);
334 data[1] = xmlNodeListGetString(prop->doc,
335 prop->xmlChildrenNode, 1);
336 if (!data[1])
337 data[1] = g_strdup("-");
338 gtk_clist_append(table, data);
339 g_free(data[0]);
340 g_free(data[1]);
345 return GTK_WIDGET(table);
348 static GtkWidget *make_file_says(guchar *path)
350 GtkWidget *frame;
351 GtkWidget *file_label;
352 int file_data[2];
353 char *argv[] = {"file", "-b", NULL, NULL};
354 FileStatus *fs = NULL;
355 guchar *tmp;
357 frame = gtk_frame_new(_("file(1) says..."));
358 file_label = gtk_label_new(_("<nothing yet>"));
359 gtk_misc_set_padding(GTK_MISC(file_label), 4, 4);
360 gtk_label_set_line_wrap(GTK_LABEL(file_label), TRUE);
361 gtk_container_add(GTK_CONTAINER(frame), file_label);
363 if (pipe(file_data))
365 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
366 gtk_label_set_text(GTK_LABEL(file_label), tmp);
367 g_free(tmp);
368 return frame;
371 switch (fork())
373 case -1:
374 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
375 gtk_label_set_text(GTK_LABEL(file_label), tmp);
376 g_free(tmp);
377 close(file_data[0]);
378 close(file_data[1]);
379 break;
380 case 0:
381 /* We are the child */
382 close(file_data[0]);
383 dup2(file_data[1], STDOUT_FILENO);
384 dup2(file_data[1], STDERR_FILENO);
385 #ifdef FILE_B_FLAG
386 argv[2] = path;
387 #else
388 tmp = strrchr(path, '/');
389 argv[1] = tmp + 1;
390 if (tmp > path)
391 chdir(g_strndup(path, tmp - path));
392 else
393 chdir("/");
394 #endif
395 if (execvp(argv[0], argv))
396 fprintf(stderr, "execvp() error: %s\n",
397 g_strerror(errno));
398 _exit(0);
399 default:
400 /* We are the parent */
401 close(file_data[1]);
402 fs = g_new(FileStatus, 1);
403 fs->label = GTK_LABEL(file_label);
404 fs->fd = file_data[0];
405 fs->text = g_strdup("");
406 fs->input = gdk_input_add(fs->fd, GDK_INPUT_READ,
407 (GdkInputFunction) add_file_output, fs);
408 gtk_signal_connect(GTK_OBJECT(frame), "destroy",
409 GTK_SIGNAL_FUNC(file_info_destroyed), fs);
410 break;
413 return frame;
416 /* Got some data from file(1) - stick it in the window. */
417 static void add_file_output(FileStatus *fs,
418 gint source, GdkInputCondition condition)
420 char buffer[20];
421 char *str;
422 int got;
424 got = read(source, buffer, sizeof(buffer) - 1);
425 if (got <= 0)
427 int err = errno;
428 gtk_input_remove(fs->input);
429 close(source);
430 fs->fd = -1;
431 if (got < 0)
432 delayed_error(_("file(1) says... %s"),
433 g_strerror(err));
434 return;
436 buffer[got] = '\0';
438 str = g_strconcat(fs->text, buffer, NULL);
439 g_free(fs->text);
440 fs->text = str;
442 str = g_strdup(fs->text);
443 g_strstrip(str);
444 gtk_label_set_text(fs->label, str);
445 g_free(str);
448 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs)
450 if (fs->fd != -1)
452 gtk_input_remove(fs->input);
453 close(fs->fd);
456 g_free(fs->text);
457 g_free(fs);
460 /* g_free() the result */
461 static guchar *pretty_type(DirItem *file, guchar *path)
463 if (file->flags & ITEM_FLAG_SYMLINK)
465 char *target;
467 target = readlink_dup(path);
468 if (target)
470 char *retval;
472 retval = g_strdup_printf(_("Symbolic link to %s"),
473 target);
474 g_free(target);
475 return retval;
478 return g_strdup(_("Symbolic link"));
481 if (file->flags & ITEM_FLAG_APPDIR)
482 return g_strdup(_("ROX application"));
484 if (file->flags & ITEM_FLAG_MOUNT_POINT)
485 return g_strdup(_("Mount point"));
487 if (file->mime_type)
488 return g_strconcat(file->mime_type->media_type, "/",
489 file->mime_type->subtype, NULL);
491 return g_strdup("-");