r2180: Code tidying (Bernard Jungen).
[rox-filer.git] / ROX-Filer / src / infobox.c
blobe9d0d28602a833027c10e7f5cb2a3257874126ff
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(const guchar *path);
62 static GtkWidget *make_details(const guchar *path, DirItem *item);
63 static GtkWidget *make_about(const guchar *path, XMLwrapper *ai);
64 static GtkWidget *make_file_says(const guchar *path);
65 static void add_file_output(FileStatus *fs,
66 gint source, GdkInputCondition condition);
67 static const gchar *pretty_type(DirItem *file, const 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 gboolean ok;
89 message = g_strdup_printf(
90 _("Are you sure you want to open %d windows?"), n);
91 ok = confirm(message, GTK_STOCK_YES, _("Show Info"));
92 g_free(message);
93 if (!ok)
94 return;
97 g_list_foreach(paths, (GFunc) infobox_new, NULL);
100 /* Create and display a new info box showing details about this item */
101 void infobox_new(const gchar *pathname)
103 GtkWidget *window, *details;
104 gchar *path;
105 GObject *owindow;
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(
112 g_utf8_validate(path, -1, NULL) ? path
113 : _("(bad utf-8)"),
114 NULL, GTK_DIALOG_NO_SEPARATOR,
115 GTK_STOCK_CANCEL, GTK_RESPONSE_DELETE_EVENT,
116 GTK_STOCK_REFRESH, GTK_RESPONSE_APPLY,
117 NULL);
119 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_MOUSE);
121 details = make_vbox(path);
122 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(window)->vbox),
123 details);
125 owindow = G_OBJECT(window);
126 g_object_set_data(owindow, "details", details);
127 g_object_set_data_full(owindow, "path", path, g_free);
129 g_signal_connect(window, "response", G_CALLBACK(got_response), NULL);
131 number_of_windows++;
132 gtk_widget_show_all(window);
135 /****************************************************************
136 * INTERNAL FUNCTIONS *
137 ****************************************************************/
139 static void show_help_clicked(const gchar *path)
141 DirItem *item;
143 item = diritem_new("");
144 diritem_restat(path, item, NULL);
145 show_item_help(path, item);
146 diritem_free(item);
149 static void got_response(GObject *window, gint response, gpointer data)
151 if (response == GTK_RESPONSE_APPLY)
152 refresh_info(window);
153 else
155 gtk_widget_destroy(GTK_WIDGET(window));
156 one_less_window();
160 static void refresh_info(GObject *window)
162 GtkWidget *details, *vbox;
163 guchar *path;
165 path = g_object_get_data(window, "path");
166 details = g_object_get_data(window, "details");
167 g_return_if_fail(details != NULL);
168 g_return_if_fail(path != NULL);
170 vbox = details->parent;
171 gtk_widget_destroy(details);
173 details = make_vbox(path);
174 g_object_set_data(window, "details", details);
175 gtk_box_pack_start_defaults(GTK_BOX(vbox), details);
176 gtk_widget_show_all(details);
179 static void add_frame(GtkBox *vbox, GtkWidget *list)
181 GtkWidget *frame;
183 frame = gtk_frame_new(NULL);
184 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
185 gtk_container_add(GTK_CONTAINER(frame), list);
186 gtk_box_pack_start_defaults(vbox, frame);
189 /* Create the VBox widget that contains the details.
190 * Note that 'path' must not be freed until the vbox is destroyed.
192 static GtkWidget *make_vbox(const guchar *path)
194 DirItem *item;
195 GtkBox *vbox;
196 XMLwrapper *ai;
197 xmlNode *about = NULL;
198 gchar *help_dir;
200 g_return_val_if_fail(path[0] == '/', NULL);
202 item = diritem_new(g_basename(path));
203 diritem_restat(path, item, NULL);
205 ai = appinfo_get(path, item);
206 if (ai)
207 about = xml_get_section(ai, NULL, "About");
209 vbox = GTK_BOX(gtk_vbox_new(FALSE, 4));
211 add_frame(vbox, make_details(path, item));
213 help_dir = g_strconcat(path, "/Help", NULL);
215 if (access(help_dir, F_OK) == 0)
217 GtkWidget *button, *align;
219 align = gtk_alignment_new(0.5, 0.5, 0, 0);
221 button = button_new_mixed(GTK_STOCK_JUMP_TO,
222 _("Show _Help Files"));
223 gtk_box_pack_start(vbox, align, FALSE, TRUE, 0);
224 gtk_container_add(GTK_CONTAINER(align), button);
225 g_signal_connect_swapped(button, "clicked",
226 G_CALLBACK(show_help_clicked),
227 (gpointer) path);
229 g_free(help_dir);
231 if (about)
232 add_frame(vbox, make_about(path, ai));
233 else
234 gtk_box_pack_start_defaults(vbox, make_file_says(path));
236 if (ai)
237 g_object_unref(ai);
239 diritem_free(item);
241 return (GtkWidget *) vbox;
244 /* The selection has changed - grab or release the primary selection */
245 static void set_selection(GtkTreeView *view, gpointer data)
247 static GtkClipboard *primary = NULL;
248 GtkTreeModel *model;
249 GtkTreePath *path = NULL;
250 GtkTreeIter iter;
251 gchar *text;
253 gtk_tree_view_get_cursor(view, &path, NULL);
254 if (!path)
255 return;
257 if (!primary)
258 primary = gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE));
260 model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
262 gtk_tree_model_get_iter(model, &iter, path);
263 gtk_tree_path_free(path);
265 gtk_tree_model_get(model, &iter, 1, &text, -1);
267 gtk_clipboard_set_text(primary, text, -1);
269 g_free(text);
272 static void add_row(GtkListStore *store, const gchar *label, const gchar *data)
274 GtkTreeIter iter;
275 gchar *u8 = NULL;
277 if (!g_utf8_validate(data, -1, NULL))
278 u8 = to_utf8(data);
280 gtk_list_store_append(store, &iter);
281 gtk_list_store_set(store, &iter, 0, label, 1, u8 ? u8 : data, -1);
283 g_free(u8);
286 static void add_row_and_free(GtkListStore *store,
287 const gchar *label, gchar *data)
289 add_row(store, label, data);
290 g_free(data);
293 /* Create an empty list view, ready to place some data in */
294 static void make_list(GtkListStore **list_store, GtkWidget **list_view)
296 GtkListStore *store;
297 GtkTreeView *view;
298 GtkCellRenderer *cell_renderer;
300 store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
301 view = GTK_TREE_VIEW(
302 gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)));
303 g_object_unref(G_OBJECT(store));
304 gtk_tree_view_set_headers_visible(view, FALSE);
306 cell_renderer = gtk_cell_renderer_text_new();
307 g_object_set(G_OBJECT(cell_renderer), "xalign", 1.0, NULL);
308 gtk_tree_view_insert_column_with_attributes(view,
309 0, NULL, cell_renderer, "text", 0, NULL);
311 cell_renderer = gtk_cell_renderer_text_new();
312 gtk_tree_view_insert_column_with_attributes(view,
313 1, NULL, cell_renderer, "text", 1, NULL);
315 g_signal_connect(view, "cursor_changed",
316 G_CALLBACK(set_selection), NULL);
318 *list_store = store;
319 *list_view = (GtkWidget *) view;
322 /* Create the TreeView widget with the file's details */
323 static GtkWidget *make_details(const guchar *path, DirItem *item)
325 GtkListStore *store;
326 GtkWidget *view;
327 gchar *tmp, *tmp2;
329 make_list(&store, &view);
331 add_row(store, _("Name:"), item->leafname);
333 if (item->base_type == TYPE_ERROR)
335 add_row(store, _("Error:"), g_strerror(item->lstat_errno));
336 return view;
339 tmp = g_path_get_dirname(path);
340 tmp2 = pathdup(tmp);
341 if (strcmp(tmp, tmp2) != 0)
342 add_row(store, _("Real directory:"), tmp2);
343 g_free(tmp);
344 g_free(tmp2);
346 add_row_and_free(store, _("Owner, Group:"),
347 g_strdup_printf("%s, %s",
348 user_name(item->uid),
349 group_name(item->gid)));
351 add_row_and_free(store, _("Size:"),
352 item->size >= PRETTY_SIZE_LIMIT
353 ? g_strdup_printf("%s (%" SIZE_FMT " %s)",
354 format_size(item->size),
355 item->size, _("bytes"))
356 : g_strdup(format_size(item->size)));
358 add_row_and_free(store, _("Change time:"), pretty_time(&item->ctime));
360 add_row_and_free(store, _("Modify time:"), pretty_time(&item->mtime));
362 add_row_and_free(store, _("Access time:"), pretty_time(&item->atime));
364 add_row(store, _("Permissions:"), pretty_permissions(item->mode));
366 add_row(store, _("Type:"), pretty_type(item, path));
368 if (item->base_type != TYPE_DIRECTORY)
369 add_row_and_free(store, _("Run action:"),
370 describe_current_command(item->mime_type));
372 return view;
375 /* Create the TreeView widget with the application's details */
376 static GtkWidget *make_about(const guchar *path, XMLwrapper *ai)
378 GtkListStore *store;
379 GtkWidget *view;
380 xmlNode *prop;
381 gchar *tmp;
382 xmlNode *about, *about_trans;
383 GHashTable *translate;
385 g_return_val_if_fail(ai != NULL, NULL);
387 about_trans = xml_get_section(ai, NULL, "About");
389 about = xmlDocGetRootElement(ai->doc)->xmlChildrenNode;
390 for (; about; about = about->next)
392 if (about->type != XML_ELEMENT_NODE)
393 continue;
394 if (about->ns == NULL && strcmp(about->name, "About") == 0)
395 break;
398 g_return_val_if_fail(about != NULL, NULL);
400 make_list(&store, &view);
402 /* Add each field in about to the list, but overriding each element
403 * with about_trans if a translation is supplied.
405 translate = g_hash_table_new_full(g_str_hash, g_str_equal,
406 NULL, g_free);
407 if (about_trans != about)
409 xmlNode *p;
410 for (p = about_trans->xmlChildrenNode; p; p = p->next)
412 if (p->type != XML_ELEMENT_NODE)
413 continue;
414 tmp = xmlNodeListGetString(p->doc,
415 p->xmlChildrenNode, 1);
416 if (tmp)
417 g_hash_table_insert(translate,
418 (char *) p->name, tmp);
423 for (prop = about->xmlChildrenNode; prop; prop = prop->next)
425 if (prop->type == XML_ELEMENT_NODE)
427 char *l;
428 char *trans;
430 trans = g_hash_table_lookup(translate, prop->name);
432 l = g_strconcat((char *) prop->name, ":", NULL);
433 if (trans)
434 tmp = g_strdup(trans);
435 else
436 tmp = xmlNodeListGetString(prop->doc,
437 prop->xmlChildrenNode, 1);
438 if (!tmp)
439 tmp = g_strdup("-");
440 add_row_and_free(store, l, tmp);
441 g_free(l);
445 g_hash_table_destroy(translate);
447 return view;
450 static GtkWidget *make_file_says(const guchar *path)
452 GtkWidget *frame;
453 GtkWidget *w_file_label;
454 GtkLabel *l_file_label;
455 int file_data[2];
456 char *argv[] = {"file", "-b", NULL, NULL};
457 FileStatus *fs = NULL;
458 guchar *tmp;
460 frame = gtk_frame_new(_("file(1) says..."));
461 w_file_label = gtk_label_new(_("<nothing yet>"));
462 l_file_label = GTK_LABEL(w_file_label);
463 gtk_misc_set_padding(GTK_MISC(w_file_label), 4, 4);
464 gtk_label_set_line_wrap(l_file_label, TRUE);
465 gtk_container_add(GTK_CONTAINER(frame), w_file_label);
467 if (pipe(file_data))
469 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
470 gtk_label_set_text(l_file_label, tmp);
471 g_free(tmp);
472 return frame;
475 switch (fork())
477 case -1:
478 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
479 gtk_label_set_text(l_file_label, tmp);
480 g_free(tmp);
481 close(file_data[0]);
482 close(file_data[1]);
483 break;
484 case 0:
485 /* We are the child */
486 close(file_data[0]);
487 dup2(file_data[1], STDOUT_FILENO);
488 dup2(file_data[1], STDERR_FILENO);
489 #ifdef FILE_B_FLAG
490 argv[2] = (char *) path;
491 #else
492 argv[1] = (char *) g_basename(path);
493 chdir(g_path_get_dirname(path));
494 #endif
495 if (execvp(argv[0], argv))
496 fprintf(stderr, "execvp() error: %s\n",
497 g_strerror(errno));
498 _exit(0);
499 default:
500 /* We are the parent */
501 close(file_data[1]);
502 fs = g_new(FileStatus, 1);
503 fs->label = l_file_label;
504 fs->fd = file_data[0];
505 fs->text = g_strdup("");
506 fs->input = gtk_input_add_full(fs->fd, GDK_INPUT_READ,
507 (GdkInputFunction) add_file_output,
508 NULL, fs, NULL);
509 g_signal_connect(frame, "destroy",
510 G_CALLBACK(file_info_destroyed), fs);
511 break;
514 return frame;
517 /* Got some data from file(1) - stick it in the window. */
518 static void add_file_output(FileStatus *fs,
519 gint source, GdkInputCondition condition)
521 char buffer[20];
522 char *str;
523 int got;
525 got = read(source, buffer, sizeof(buffer) - 1);
526 if (got <= 0)
528 int err = errno;
529 gtk_input_remove(fs->input);
530 close(source);
531 fs->fd = -1;
532 if (got < 0)
533 delayed_error(_("file(1) says... %s"),
534 g_strerror(err));
535 return;
537 buffer[got] = '\0';
539 str = g_strconcat(fs->text, buffer, NULL);
540 g_free(fs->text);
541 fs->text = str;
543 str = to_utf8(fs->text);
544 g_strstrip(str);
545 gtk_label_set_text(fs->label, str);
546 g_free(str);
549 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs)
551 if (fs->fd != -1)
553 gtk_input_remove(fs->input);
554 close(fs->fd);
557 g_free(fs->text);
558 g_free(fs);
561 /* Don't g_free() the result */
562 static const gchar *pretty_type(DirItem *file, const guchar *path)
564 static gchar *text = NULL;
566 null_g_free(&text);
568 if (file->flags & ITEM_FLAG_SYMLINK)
570 char *target;
572 target = readlink_dup(path);
573 if (target)
575 ensure_utf8(&target);
577 text = g_strdup_printf(_("Symbolic link to %s"),
578 target);
579 g_free(target);
580 return text;
583 return _("Symbolic link");
586 if (file->flags & ITEM_FLAG_APPDIR)
587 return _("ROX application");
589 if (file->flags & ITEM_FLAG_MOUNT_POINT)
591 MountPoint *mp;
592 const gchar *mounted;
594 mounted = mount_is_mounted(path, NULL, NULL)
595 ? _("mounted") : _("unmounted");
597 mp = g_hash_table_lookup(fstab_mounts, path);
598 if (mp)
599 text = g_strdup_printf(_("Mount point for %s (%s)"),
600 mp->name, mounted);
601 else
602 text = g_strdup_printf(_("Mount point (%s)"), mounted);
603 return text;
606 if (file->mime_type)
608 text = g_strconcat(file->mime_type->media_type, "/",
609 file->mime_type->subtype, NULL);
610 return text;
613 return "-";