r1476: Options to forward button-3 clicks on the pinboard to the window manager
[rox-filer.git] / ROX-Filer / src / infobox.c
blobdefbba4b466a4b18330d293215f69072e2201ed7
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 "xml.h"
46 typedef struct _FileStatus FileStatus;
48 /* This is for the 'file(1) says...' thing */
49 struct _FileStatus
51 int fd; /* FD to read from, -1 if closed */
52 int input; /* Input watcher tag if fd valid */
53 GtkLabel *label; /* Widget to output to */
54 gchar *text; /* String so far */
57 /* Static prototypes */
58 static void refresh_info(GObject *window);
59 static GtkWidget *make_vbox(guchar *path);
60 static GtkWidget *make_details(guchar *path, DirItem *item, xmlNode *about);
61 static GtkWidget *make_file_says(guchar *path);
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 got_response(GObject *window, gint response, gpointer data);
67 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs);
69 /****************************************************************
70 * EXTERNAL INTERFACE *
71 ****************************************************************/
73 /* Create and display a new info box showing details about this item */
74 void infobox_new(const gchar *pathname)
76 GtkWidget *window, *details;
77 gchar *path;
79 g_return_if_fail(pathname != NULL);
81 path = g_strdup(pathname); /* Gets attached to window & freed later */
83 window = gtk_dialog_new_with_buttons(path,
84 NULL, GTK_DIALOG_NO_SEPARATOR,
85 GTK_STOCK_CANCEL, GTK_RESPONSE_DELETE_EVENT,
86 GTK_STOCK_REFRESH, GTK_RESPONSE_APPLY,
87 NULL);
89 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_MOUSE);
91 details = make_vbox(path);
92 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox),
93 details, TRUE, TRUE, 0);
95 g_object_set_data(G_OBJECT(window), "details", details);
96 g_object_set_data_full(G_OBJECT(window), "path", path, g_free);
98 g_signal_connect(window, "response", G_CALLBACK(got_response), NULL);
100 number_of_windows++;
101 gtk_widget_show_all(window);
104 /****************************************************************
105 * INTERNAL FUNCTIONS *
106 ****************************************************************/
108 static void got_response(GObject *window, gint response, gpointer data)
110 if (response == GTK_RESPONSE_APPLY)
111 refresh_info(window);
112 else
114 gtk_widget_destroy(GTK_WIDGET(window));
115 if (--number_of_windows < 1)
116 gtk_main_quit();
120 static void refresh_info(GObject *window)
122 GtkWidget *details, *vbox;
123 guchar *path;
125 path = g_object_get_data(window, "path");
126 details = g_object_get_data(window, "details");
127 g_return_if_fail(details != NULL);
128 g_return_if_fail(path != NULL);
130 vbox = details->parent;
131 gtk_widget_destroy(details);
133 details = make_vbox(path);
134 g_object_set_data(window, "details", details);
135 gtk_box_pack_start(GTK_BOX(vbox), details, TRUE, TRUE, 0);
136 gtk_widget_show_all(details);
139 /* Create the VBox widget that contains the details */
140 static GtkWidget *make_vbox(guchar *path)
142 DirItem *item;
143 GtkWidget *vbox, *list, *file, *frame;
144 XMLwrapper *ai;
145 xmlNode *about = NULL;
147 g_return_val_if_fail(path[0] == '/', NULL);
149 item = diritem_new(g_basename(path));
150 diritem_restat(path, item, NULL);
152 vbox = gtk_vbox_new(FALSE, 4);
154 ai = appinfo_get(path, item);
155 if (ai)
156 about = xml_get_section(ai, NULL, "About");
158 frame = gtk_frame_new(NULL);
159 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
160 list = make_details(path, item, about);
161 gtk_container_add(GTK_CONTAINER(frame), list);
162 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
164 if (!about)
166 file = make_file_says(path);
167 gtk_box_pack_start(GTK_BOX(vbox), file, TRUE, TRUE, 0);
170 if (ai)
171 g_object_unref(ai);
173 diritem_free(item);
175 return vbox;
178 /* The selection has changed - grab or release the primary selection */
179 static void set_selection(GtkTreeView *view, gpointer data)
181 static GtkClipboard *primary = NULL;
182 GtkTreeModel *model;
183 GtkTreePath *path = NULL;
184 GtkTreeIter iter;
185 gchar *text;
187 gtk_tree_view_get_cursor(view, &path, NULL);
188 if (!path)
189 return;
191 if (!primary)
192 primary = gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE));
194 model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
196 gtk_tree_model_get_iter(model, &iter, path);
197 gtk_tree_path_free(path);
199 gtk_tree_model_get(model, &iter, 1, &text, -1);
201 gtk_clipboard_set_text(primary, text, -1);
203 g_free(text);
206 static void add_row(GtkListStore *store, const gchar *label, const gchar *data)
208 GtkTreeIter iter;
210 gtk_list_store_append(store, &iter);
211 gtk_list_store_set(store, &iter, 0, label, 1, data, -1);
214 /* Create the TreeView widget with the file's details */
215 static GtkWidget *make_details(guchar *path, DirItem *item, xmlNode *about)
217 GtkListStore *store;
218 GtkWidget *view;
219 GtkCellRenderer *cell_renderer;
220 GString *gstring;
221 struct stat info;
222 xmlNode *prop;
223 gchar *tmp;
225 store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
226 view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
227 g_object_unref(G_OBJECT(store));
228 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
230 cell_renderer = gtk_cell_renderer_text_new();
231 g_object_set(G_OBJECT(cell_renderer), "xalign", 1.0, NULL);
232 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
233 0, NULL, cell_renderer, "text", 0, NULL);
235 cell_renderer = gtk_cell_renderer_text_new();
236 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
237 1, NULL, cell_renderer, "text", 1, NULL);
239 g_signal_connect(view, "cursor_changed",
240 G_CALLBACK(set_selection), NULL);
242 add_row(store, _("Name:"), item->leafname);
244 if (lstat(path, &info))
246 add_row(store, _("Error:"), g_strerror(errno));
247 return view;
250 gstring = g_string_new(NULL);
252 g_string_sprintf(gstring, "%s, %s", user_name(info.st_uid),
253 group_name(info.st_gid));
254 add_row(store, _("Owner, Group:"), gstring->str);
256 if (info.st_size >= PRETTY_SIZE_LIMIT)
258 g_string_sprintf(gstring, "%s (%" SIZE_FMT " %s)",
259 format_size(info.st_size),
260 info.st_size, _("bytes"));
262 else
264 g_string_assign(gstring,
265 format_size(info.st_size));
267 add_row(store, _("Size:"), gstring->str);
269 tmp = pretty_time(&info.st_ctime);
270 add_row(store, _("Change time:"), tmp);
271 g_free(tmp);
273 tmp = pretty_time(&info.st_mtime);
274 add_row(store, _("Modify time:"), tmp);
275 g_free(tmp);
277 tmp = pretty_time(&info.st_atime);
278 add_row(store, _("Access time:"), tmp);
279 g_free(tmp);
281 g_string_free(gstring, TRUE);
283 add_row(store, _("Permissions:"), pretty_permissions(info.st_mode));
285 tmp = pretty_type(item, path);
286 add_row(store, _("Type:"), tmp);
287 g_free(tmp);
289 if (item->base_type != TYPE_DIRECTORY)
291 tmp = describe_current_command(item->mime_type);
292 add_row(store, _("Run action:"), tmp);
293 g_free(tmp);
296 if (about)
298 add_row(store, "", "");
299 //gtk_clist_set_selectable(table, table->rows - 1, FALSE);
300 for (prop = about->xmlChildrenNode; prop; prop = prop->next)
302 if (prop->type == XML_ELEMENT_NODE)
304 char *l;
306 l = g_strconcat((char *) prop->name, ":", NULL);
307 tmp = xmlNodeListGetString(prop->doc,
308 prop->xmlChildrenNode, 1);
309 if (!tmp)
310 tmp = g_strdup("-");
311 add_row(store, l, tmp);
312 g_free(l);
313 g_free(tmp);
318 return view;
321 static GtkWidget *make_file_says(guchar *path)
323 GtkWidget *frame;
324 GtkWidget *file_label;
325 int file_data[2];
326 char *argv[] = {"file", "-b", NULL, NULL};
327 FileStatus *fs = NULL;
328 guchar *tmp;
330 frame = gtk_frame_new(_("file(1) says..."));
331 file_label = gtk_label_new(_("<nothing yet>"));
332 gtk_misc_set_padding(GTK_MISC(file_label), 4, 4);
333 gtk_label_set_line_wrap(GTK_LABEL(file_label), TRUE);
334 gtk_container_add(GTK_CONTAINER(frame), file_label);
336 if (pipe(file_data))
338 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
339 gtk_label_set_text(GTK_LABEL(file_label), tmp);
340 g_free(tmp);
341 return frame;
344 switch (fork())
346 case -1:
347 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
348 gtk_label_set_text(GTK_LABEL(file_label), tmp);
349 g_free(tmp);
350 close(file_data[0]);
351 close(file_data[1]);
352 break;
353 case 0:
354 /* We are the child */
355 close(file_data[0]);
356 dup2(file_data[1], STDOUT_FILENO);
357 dup2(file_data[1], STDERR_FILENO);
358 #ifdef FILE_B_FLAG
359 argv[2] = path;
360 #else
361 tmp = strrchr(path, '/');
362 argv[1] = tmp + 1;
363 if (tmp > path)
364 chdir(g_strndup(path, tmp - path));
365 else
366 chdir("/");
367 #endif
368 if (execvp(argv[0], argv))
369 fprintf(stderr, "execvp() error: %s\n",
370 g_strerror(errno));
371 _exit(0);
372 default:
373 /* We are the parent */
374 close(file_data[1]);
375 fs = g_new(FileStatus, 1);
376 fs->label = GTK_LABEL(file_label);
377 fs->fd = file_data[0];
378 fs->text = g_strdup("");
379 fs->input = gtk_input_add_full(fs->fd, GDK_INPUT_READ,
380 (GdkInputFunction) add_file_output,
381 NULL, fs, NULL);
382 g_signal_connect(frame, "destroy",
383 G_CALLBACK(file_info_destroyed), fs);
384 break;
387 return frame;
390 /* Got some data from file(1) - stick it in the window. */
391 static void add_file_output(FileStatus *fs,
392 gint source, GdkInputCondition condition)
394 char buffer[20];
395 char *str;
396 int got;
398 got = read(source, buffer, sizeof(buffer) - 1);
399 if (got <= 0)
401 int err = errno;
402 gtk_input_remove(fs->input);
403 close(source);
404 fs->fd = -1;
405 if (got < 0)
406 delayed_error(_("file(1) says... %s"),
407 g_strerror(err));
408 return;
410 buffer[got] = '\0';
412 str = g_strconcat(fs->text, buffer, NULL);
413 g_free(fs->text);
414 fs->text = str;
416 str = g_strdup(fs->text);
417 g_strstrip(str);
418 gtk_label_set_text(fs->label, str);
419 g_free(str);
422 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs)
424 if (fs->fd != -1)
426 gtk_input_remove(fs->input);
427 close(fs->fd);
430 g_free(fs->text);
431 g_free(fs);
434 /* g_free() the result */
435 static guchar *pretty_type(DirItem *file, guchar *path)
437 if (file->flags & ITEM_FLAG_SYMLINK)
439 char *target;
441 target = readlink_dup(path);
442 if (target)
444 char *retval;
446 retval = g_strdup_printf(_("Symbolic link to %s"),
447 target);
448 g_free(target);
449 return retval;
452 return g_strdup(_("Symbolic link"));
455 if (file->flags & ITEM_FLAG_APPDIR)
456 return g_strdup(_("ROX application"));
458 if (file->flags & ITEM_FLAG_MOUNT_POINT)
459 return g_strdup(_("Mount point"));
461 if (file->mime_type)
462 return g_strconcat(file->mime_type->media_type, "/",
463 file->mime_type->subtype, NULL);
465 return g_strdup("-");