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)
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
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 */
29 #include <sys/param.h>
30 #include <libxml/parser.h>
38 #include "gui_support.h"
43 #include "dnd.h" /* For xa_string */
44 #include "run.h" /* For show_item_help() */
48 typedef struct _FileStatus FileStatus
;
50 /* This is for the 'file(1) says...' thing */
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
)
82 n
= g_list_length(paths
);
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"));
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
;
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
114 NULL
, GTK_DIALOG_NO_SEPARATOR
,
115 GTK_STOCK_CANCEL
, GTK_RESPONSE_DELETE_EVENT
,
116 GTK_STOCK_REFRESH
, GTK_RESPONSE_APPLY
,
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
),
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
);
132 gtk_widget_show_all(window
);
135 /****************************************************************
136 * INTERNAL FUNCTIONS *
137 ****************************************************************/
139 static void show_help_clicked(const gchar
*path
)
143 item
= diritem_new("");
144 diritem_restat(path
, item
, NULL
);
145 show_item_help(path
, item
);
149 static void got_response(GObject
*window
, gint response
, gpointer data
)
151 if (response
== GTK_RESPONSE_APPLY
)
152 refresh_info(window
);
155 gtk_widget_destroy(GTK_WIDGET(window
));
160 static void refresh_info(GObject
*window
)
162 GtkWidget
*details
, *vbox
;
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
)
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
)
197 xmlNode
*about
= NULL
;
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
);
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
),
232 add_frame(vbox
, make_about(path
, ai
));
234 gtk_box_pack_start_defaults(vbox
, make_file_says(path
));
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
;
249 GtkTreePath
*path
= NULL
;
253 gtk_tree_view_get_cursor(view
, &path
, NULL
);
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);
272 static void add_row(GtkListStore
*store
, const gchar
*label
, const gchar
*data
)
277 if (!g_utf8_validate(data
, -1, NULL
))
280 gtk_list_store_append(store
, &iter
);
281 gtk_list_store_set(store
, &iter
, 0, label
, 1, u8
? u8
: data
, -1);
286 static void add_row_and_free(GtkListStore
*store
,
287 const gchar
*label
, gchar
*data
)
289 add_row(store
, label
, data
);
293 /* Create an empty list view, ready to place some data in */
294 static void make_list(GtkListStore
**list_store
, GtkWidget
**list_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
);
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
)
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
));
339 tmp
= g_path_get_dirname(path
);
341 if (strcmp(tmp
, tmp2
) != 0)
342 add_row(store
, _("Real directory:"), 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
));
375 /* Create the TreeView widget with the application's details */
376 static GtkWidget
*make_about(const guchar
*path
, XMLwrapper
*ai
)
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
)
394 if (about
->ns
== NULL
&& strcmp(about
->name
, "About") == 0)
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
,
407 if (about_trans
!= about
)
410 for (p
= about_trans
->xmlChildrenNode
; p
; p
= p
->next
)
412 if (p
->type
!= XML_ELEMENT_NODE
)
414 tmp
= xmlNodeListGetString(p
->doc
,
415 p
->xmlChildrenNode
, 1);
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
)
430 trans
= g_hash_table_lookup(translate
, prop
->name
);
432 l
= g_strconcat((char *) prop
->name
, ":", NULL
);
434 tmp
= g_strdup(trans
);
436 tmp
= xmlNodeListGetString(prop
->doc
,
437 prop
->xmlChildrenNode
, 1);
440 add_row_and_free(store
, l
, tmp
);
445 g_hash_table_destroy(translate
);
450 static GtkWidget
*make_file_says(const guchar
*path
)
453 GtkWidget
*w_file_label
;
454 GtkLabel
*l_file_label
;
456 char *argv
[] = {"file", "-b", NULL
, NULL
};
457 FileStatus
*fs
= NULL
;
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
);
469 tmp
= g_strdup_printf("pipe(): %s", g_strerror(errno
));
470 gtk_label_set_text(l_file_label
, tmp
);
478 tmp
= g_strdup_printf("pipe(): %s", g_strerror(errno
));
479 gtk_label_set_text(l_file_label
, tmp
);
485 /* We are the child */
487 dup2(file_data
[1], STDOUT_FILENO
);
488 dup2(file_data
[1], STDERR_FILENO
);
490 argv
[2] = (char *) path
;
492 argv
[1] = (char *) g_basename(path
);
493 chdir(g_path_get_dirname(path
));
495 if (execvp(argv
[0], argv
))
496 fprintf(stderr
, "execvp() error: %s\n",
500 /* We are the parent */
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
,
509 g_signal_connect(frame
, "destroy",
510 G_CALLBACK(file_info_destroyed
), fs
);
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
)
525 got
= read(source
, buffer
, sizeof(buffer
) - 1);
529 gtk_input_remove(fs
->input
);
533 delayed_error(_("file(1) says... %s"),
539 str
= g_strconcat(fs
->text
, buffer
, NULL
);
543 str
= to_utf8(fs
->text
);
545 gtk_label_set_text(fs
->label
, str
);
549 static void file_info_destroyed(GtkWidget
*widget
, FileStatus
*fs
)
553 gtk_input_remove(fs
->input
);
561 /* Don't g_free() the result */
562 static const gchar
*pretty_type(DirItem
*file
, const guchar
*path
)
564 static gchar
*text
= NULL
;
568 if (file
->flags
& ITEM_FLAG_SYMLINK
)
572 target
= readlink_dup(path
);
575 ensure_utf8(&target
);
577 text
= g_strdup_printf(_("Symbolic link to %s"),
583 return _("Symbolic link");
586 if (file
->flags
& ITEM_FLAG_APPDIR
)
587 return _("ROX application");
589 if (file
->flags
& ITEM_FLAG_MOUNT_POINT
)
592 const gchar
*mounted
;
594 mounted
= mount_is_mounted(path
, NULL
, NULL
)
595 ? _("mounted") : _("unmounted");
597 mp
= g_hash_table_lookup(fstab_mounts
, path
);
599 text
= g_strdup_printf(_("Mount point for %s (%s)"),
602 text
= g_strdup_printf(_("Mount point (%s)"), mounted
);
608 text
= g_strconcat(file
->mime_type
->media_type
, "/",
609 file
->mime_type
->subtype
, NULL
);