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 */
45 typedef struct _FileStatus FileStatus
;
47 /* This is for the 'file(1) says...' thing */
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
;
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
);
84 gtk_window_set_type_hint(GTK_WINDOW(window
),
85 GDK_WINDOW_TYPE_HINT_DIALOG
);
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
),
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
),
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
);
122 gtk_widget_show_all(window
);
125 /****************************************************************
126 * INTERNAL FUNCTIONS *
127 ****************************************************************/
129 static void info_destroyed(gpointer data
)
131 if (--number_of_windows
< 1)
135 static void refresh_info(GtkObject
*window
)
137 GtkWidget
*details
, *vbox
;
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
)
158 GtkWidget
*vbox
, *list
, *file
;
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
);
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);
179 file
= make_file_says(path
);
180 gtk_box_pack_start(GTK_BOX(vbox
), file
, TRUE
, TRUE
, 0);
186 item
->leafname
= NULL
;
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
)
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
,
210 g_free(selection_text
);
211 selection_text
= g_strdup(text
);
215 static void get_selection(GtkCList
*clist
,
216 GtkSelectionData
*selection_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
)
233 char *data
[] = {NULL
, NULL
, NULL
};
235 GtkTargetEntry target_table
[] = {
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
),
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
,
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"));
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
);
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
);
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
,
334 data
[1] = xmlNodeListGetString(prop
->doc
,
335 prop
->xmlChildrenNode
, 1);
337 data
[1] = g_strdup("-");
338 gtk_clist_append(table
, data
);
345 return GTK_WIDGET(table
);
348 static GtkWidget
*make_file_says(guchar
*path
)
351 GtkWidget
*file_label
;
353 char *argv
[] = {"file", "-b", NULL
, NULL
};
354 FileStatus
*fs
= NULL
;
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
);
365 tmp
= g_strdup_printf("pipe(): %s", g_strerror(errno
));
366 gtk_label_set_text(GTK_LABEL(file_label
), tmp
);
374 tmp
= g_strdup_printf("pipe(): %s", g_strerror(errno
));
375 gtk_label_set_text(GTK_LABEL(file_label
), tmp
);
381 /* We are the child */
383 dup2(file_data
[1], STDOUT_FILENO
);
384 dup2(file_data
[1], STDERR_FILENO
);
388 tmp
= strrchr(path
, '/');
391 chdir(g_strndup(path
, tmp
- path
));
395 if (execvp(argv
[0], argv
))
396 fprintf(stderr
, "execvp() error: %s\n",
400 /* We are the parent */
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
);
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
)
424 got
= read(source
, buffer
, sizeof(buffer
) - 1);
428 gtk_input_remove(fs
->input
);
432 delayed_error(_("file(1) says... %s"),
438 str
= g_strconcat(fs
->text
, buffer
, NULL
);
442 str
= g_strdup(fs
->text
);
444 gtk_label_set_text(fs
->label
, str
);
448 static void file_info_destroyed(GtkWidget
*widget
, FileStatus
*fs
)
452 gtk_input_remove(fs
->input
);
460 /* g_free() the result */
461 static guchar
*pretty_type(DirItem
*file
, guchar
*path
)
463 if (file
->flags
& ITEM_FLAG_SYMLINK
)
467 target
= readlink_dup(path
);
472 retval
= g_strdup_printf(_("Symbolic link to %s"),
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"));
488 return g_strconcat(file
->mime_type
->media_type
, "/",
489 file
->mime_type
->subtype
, NULL
);
491 return g_strdup("-");