2
* C Implementation: file_properties
7
* Author: Hong Jen Yee (PCMan) <pcman.tw (AT) gmail.com>, (C) 2006
9
* Copyright: See COPYING file that comes with this distribution
22
#include "ptk-file-properties.h"
23
#include "ptk-ui-xml.h"
25
#include "mime-type/mime-type.h"
27
#include <sys/types.h>
32
#include "ptk-file-task.h"
33
#include "ptk-utils.h"
35
#include "vfs-file-info.h"
36
#include "vfs-app-desktop.h"
37
#include "ptk-app-chooser.h"
39
const char* chmod_names[] =
41
"owner_r", "owner_w", "owner_x",
42
"group_r", "group_w", "group_x",
43
"others_r", "others_w", "others_x",
44
"set_uid", "set_gid", "sticky"
58
GtkToggleButton* chmod_btns[ N_CHMOD_ACTIONS ];
59
guchar chmod_states[ N_CHMOD_ACTIONS ];
61
GtkLabel* total_size_label;
62
GtkLabel* size_on_disk_label;
68
GThread* calc_size_thread;
69
guint update_label_timer;
71
FilePropertiesDialogData;
74
on_dlg_response ( GtkDialog *dialog,
79
* void get_total_size_of_dir( const char* path, off_t* size )
80
* Recursively count total size of all files in the specified directory.
81
* If the path specified is a file, the size of the file is directly returned.
82
* cancel is used to cancel the operation. This function will check the value
83
* pointed by cancel in every iteration. If cancel is set to TRUE, the
84
* calculation is cancelled.
85
* NOTE: path is encoded in on-disk encoding and not necessarily UTF-8.
87
static void calc_total_size_of_files( const char* path, FilePropertiesDialogData* data )
92
struct stat file_stat;
97
if ( lstat( path, &file_stat ) )
100
data->total_size += file_stat.st_size;
101
data->size_on_disk += ( file_stat.st_blocks << 9 ); /* block x 512 */
104
dir = g_dir_open( path, 0, NULL );
107
while ( !data->cancel && ( name = g_dir_read_name( dir ) ) )
109
full_path = g_build_filename( path, name, NULL );
110
lstat( full_path, &file_stat );
111
if ( S_ISDIR( file_stat.st_mode ) )
113
calc_total_size_of_files( full_path, data );
117
data->total_size += file_stat.st_size;
118
data->size_on_disk += ( file_stat.st_blocks << 9 );
127
static gpointer calc_size( gpointer user_data )
129
FilePropertiesDialogData * data = ( FilePropertiesDialogData* ) user_data;
133
for ( l = data->file_list; l; l = l->next )
137
file = ( VFSFileInfo* ) l->data;
138
path = g_build_filename( data->dir_path,
139
vfs_file_info_get_name( file ), NULL );
142
calc_total_size_of_files( path, data );
150
gboolean on_update_labels( FilePropertiesDialogData* data )
157
vfs_file_size_to_string( buf2, data->total_size );
158
sprintf( buf, "%s (%llu Bytes)", buf2, ( guint64 ) data->total_size );
159
gtk_label_set_text( data->total_size_label, buf );
161
vfs_file_size_to_string( buf2, data->size_on_disk );
162
sprintf( buf, "%s (%llu Bytes)", buf2, ( guint64 ) data->size_on_disk );
163
gtk_label_set_text( data->size_on_disk_label, buf );
170
static void on_chmod_btn_toggled( GtkToggleButton* btn,
171
FilePropertiesDialogData* data )
173
/* Bypass the default handler */
174
g_signal_stop_emission_by_name( btn, "toggled" );
175
/* Block this handler while we are changing the state of buttons,
176
or this handler will be called recursively. */
177
g_signal_handlers_block_matched( btn, G_SIGNAL_MATCH_FUNC, 0,
178
0, NULL, on_chmod_btn_toggled, NULL );
180
if ( gtk_toggle_button_get_inconsistent( btn ) )
182
gtk_toggle_button_set_inconsistent( btn, FALSE );
183
gtk_toggle_button_set_active( btn, FALSE );
185
else if ( ! gtk_toggle_button_get_active( btn ) )
187
gtk_toggle_button_set_inconsistent( btn, TRUE );
190
g_signal_handlers_unblock_matched( btn, G_SIGNAL_MATCH_FUNC, 0,
191
0, NULL, on_chmod_btn_toggled, NULL );
194
static gboolean combo_sep( GtkTreeModel *model,
199
for( i = 2; i > 0; --i )
202
gtk_tree_model_get( model, it, i, &tmp, -1 );
212
static void on_combo_change( GtkComboBox* combo, gpointer user_data )
215
if( gtk_combo_box_get_active_iter(combo, &it) )
218
GtkTreeModel* model = gtk_combo_box_get_model( combo );
219
gtk_tree_model_get( model, &it, 2, &action, -1 );
224
VFSMimeType* mime = (VFSMimeType*)user_data;
225
parent = gtk_widget_get_toplevel( GTK_WIDGET( combo ) );
226
action = (char *) ptk_choose_app_for_mime_type( GTK_WINDOW(parent),
230
gboolean exist = FALSE;
231
/* check if the action is already in the list */
232
if( gtk_tree_model_get_iter_first( model, &it ) )
237
gtk_tree_model_get( model, &it, 2, &tmp, -1 );
240
if( 0 == strcmp( tmp, action ) )
247
} while( gtk_tree_model_iter_next( model, &it ) );
250
if( ! exist ) /* It didn't exist */
252
VFSAppDesktop* app = vfs_app_desktop_new( action );
256
icon = vfs_app_desktop_get_icon( app, 20, TRUE );
257
gtk_list_store_insert_with_values(
258
GTK_LIST_STORE( model ), &it, 0,
260
1, vfs_app_desktop_get_disp_name(app),
263
gdk_pixbuf_unref( icon );
264
vfs_app_desktop_unref( app );
270
gtk_combo_box_set_active_iter( combo, &it );
276
prev_sel = GPOINTER_TO_INT( g_object_get_data( G_OBJECT(combo), "prev_sel") );
277
gtk_combo_box_set_active( combo, prev_sel );
282
int prev_sel = gtk_combo_box_get_active( combo );
283
g_object_set_data( G_OBJECT(combo), "prev_sel", GINT_TO_POINTER(prev_sel) );
288
g_object_set_data( G_OBJECT(combo), "prev_sel", GINT_TO_POINTER(-1) );
292
GtkWidget* file_properties_dlg_new( GtkWindow* parent,
293
const char* dir_path,
296
GtkWidget * dlg = ptk_ui_xml_create_widget_from_file( PACKAGE_UI_DIR "/file_properties.glade" );
298
FilePropertiesDialogData* data;
299
gboolean need_calc_size = TRUE;
301
VFSFileInfo *file, *file2;
304
const char* multiple_files = _( "Multiple files are selected" );
305
const char* calculating;
306
GtkWidget* name = ptk_ui_xml_get_widget( dlg, "file_name" );
307
GtkWidget* location = ptk_ui_xml_get_widget( dlg, "location" );
308
GtkWidget* mime_type = ptk_ui_xml_get_widget( dlg, "mime_type" );
309
GtkWidget* open_with = ptk_ui_xml_get_widget( dlg, "open_with" );
311
GtkWidget* mtime = ptk_ui_xml_get_widget( dlg, "mtime" );
312
GtkWidget* atime = ptk_ui_xml_get_widget( dlg, "atime" );
316
const char* time_format = "%Y-%m-%d %H:%M";
323
gboolean same_type = TRUE;
324
char *owner_group, *tmp;
326
gtk_dialog_set_alternative_button_order( dlg, GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1 );
327
gtk_window_set_transient_for( GTK_WINDOW( dlg ), parent );
329
data = g_slice_new0( FilePropertiesDialogData );
330
/* FIXME: When will the data be freed??? */
331
g_object_set_data( G_OBJECT( dlg ), "DialogData", data );
332
data->file_list = sel_files;
335
data->dir_path = g_strdup( dir_path );
336
disp_path = g_filename_display_name( dir_path );
337
gtk_label_set_text( GTK_LABEL( location ), disp_path );
340
data->total_size_label = GTK_LABEL( ptk_ui_xml_get_widget( dlg, "total_size" ) );
341
data->size_on_disk_label = GTK_LABEL( ptk_ui_xml_get_widget( dlg, "size_on_disk" ) );
342
data->owner = GTK_ENTRY( ptk_ui_xml_get_widget( dlg, "owner" ) );
343
data->group = GTK_ENTRY( ptk_ui_xml_get_widget( dlg, "group" ) );
345
for ( i = 0; i < N_CHMOD_ACTIONS; ++i )
347
data->chmod_btns[ i ] = GTK_TOGGLE_BUTTON( ptk_ui_xml_get_widget( dlg, chmod_names[ i ] ) );
350
for ( l = sel_files; l && l->next; l = l->next )
352
VFSMimeType *type, *type2;
353
file = ( VFSFileInfo* ) l->data;
354
file2 = ( VFSFileInfo* ) l->next->data;
355
type = vfs_file_info_get_mime_type( file );
356
type2 = vfs_file_info_get_mime_type( file2 );
359
vfs_mime_type_unref( type );
360
vfs_mime_type_unref( type2 );
364
vfs_mime_type_unref( type );
365
vfs_mime_type_unref( type2 );
368
file = ( VFSFileInfo* ) sel_files->data;
371
mime = vfs_file_info_get_mime_type( file );
372
file_type = g_strdup_printf( "%s (%s)",
373
vfs_mime_type_get_description( mime ),
374
vfs_mime_type_get_type( mime ) );
375
gtk_label_set_text( GTK_LABEL( mime_type ), file_type );
377
vfs_mime_type_unref( mime );
381
gtk_label_set_text( GTK_LABEL( mime_type ), _( "Multiple files of different types" ) );
385
* Don't show this option menu if files of different types are selected,
386
* ,the selected file is a folder, or its type is unknown.
389
vfs_file_info_is_dir( file ) ||
390
vfs_file_info_is_desktop_entry( file ) ||
391
vfs_file_info_is_unknown_type( file ) ||
392
vfs_file_info_is_executable( file, NULL ) )
394
/* if open with shouldn't show, destroy it. */
395
gtk_widget_destroy( open_with );
396
gtk_widget_destroy( ptk_ui_xml_get_widget( dlg, "open_with_label" ) );
398
else /* Add available actions to the option menu */
401
char **action, **actions;
403
mime = vfs_file_info_get_mime_type( file );
404
actions = vfs_mime_type_get_actions( mime );
405
GtkCellRenderer* renderer;
407
gtk_cell_layout_clear( GTK_CELL_LAYOUT(open_with) );
408
renderer = gtk_cell_renderer_pixbuf_new();
409
gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(open_with), renderer, FALSE);
410
gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(open_with), renderer,
412
renderer = gtk_cell_renderer_text_new();
413
gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(open_with), renderer, TRUE);
414
gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(open_with),renderer,
416
model = gtk_list_store_new( 3, GDK_TYPE_PIXBUF,
422
for( action = actions; *action; ++action )
424
VFSAppDesktop* desktop;
426
desktop = vfs_app_desktop_new( *action );
427
gtk_list_store_append( model, &it );
428
icon = vfs_app_desktop_get_icon(desktop, 20, TRUE);
429
gtk_list_store_set( model, &it,
431
1, vfs_app_desktop_get_disp_name(desktop),
434
gdk_pixbuf_unref( icon );
435
vfs_app_desktop_unref( desktop );
440
g_object_set_data( G_OBJECT(open_with), "prev_sel", GINT_TO_POINTER(-1) );
444
gtk_list_store_append( model, &it );
446
gtk_list_store_append( model, &it );
447
gtk_list_store_set( model, &it,
449
1, _("Other program..."), -1 );
450
gtk_combo_box_set_model( GTK_COMBO_BOX(open_with),
451
GTK_TREE_MODEL(model) );
452
gtk_combo_box_set_row_separator_func(
453
GTK_COMBO_BOX(open_with), combo_sep,
455
gtk_combo_box_set_active(GTK_COMBO_BOX(open_with), 0);
456
g_signal_connect( open_with, "changed",
457
G_CALLBACK(on_combo_change), mime );
459
/* vfs_mime_type_unref( mime ); */
460
/* We can unref mime when combo box gets destroyed */
461
g_object_weak_ref( G_OBJECT(open_with),
462
(GWeakNotify)vfs_mime_type_unref, mime );
465
/* Multiple files are selected */
466
if ( sel_files && sel_files->next )
468
gtk_widget_set_sensitive( name, FALSE );
469
gtk_entry_set_text( GTK_ENTRY( name ), multiple_files );
471
gtk_label_set_text( GTK_LABEL( mtime ), multiple_files );
472
gtk_label_set_text( GTK_LABEL( atime ), multiple_files );
474
for ( i = 0; i < N_CHMOD_ACTIONS; ++i )
476
gtk_toggle_button_set_inconsistent ( data->chmod_btns[ i ], TRUE );
477
data->chmod_states[ i ] = 2; /* Don't touch this bit */
478
g_signal_connect( G_OBJECT( data->chmod_btns[ i ] ), "toggled",
479
G_CALLBACK( on_chmod_btn_toggled ), data );
484
/* special processing for files with special display names */
485
if( vfs_file_info_is_desktop_entry( file ) )
487
char* disp_name = g_filename_display_name( file->name );
488
gtk_entry_set_text( GTK_ENTRY( name ),
493
gtk_entry_set_text( GTK_ENTRY( name ),
494
vfs_file_info_get_disp_name( file ) );
496
gtk_editable_set_editable ( GTK_EDITABLE( name ), FALSE );
498
if ( ! vfs_file_info_is_dir( file ) )
500
/* Only single "file" is selected, so we don't need to
501
caculate total file size */
502
need_calc_size = FALSE;
504
sprintf( buf, "%s (%llu Bytes)",
505
vfs_file_info_get_disp_size( file ),
506
( guint64 ) vfs_file_info_get_size( file ) );
507
gtk_label_set_text( data->total_size_label, buf );
509
vfs_file_size_to_string( buf2,
510
vfs_file_info_get_blocks( file ) * 512 );
511
sprintf( buf, "%s (%llu Bytes)", buf2,
512
( guint64 ) vfs_file_info_get_blocks( file ) * 512 );
513
gtk_label_set_text( data->size_on_disk_label, buf );
515
gtk_label_set_text( GTK_LABEL( mtime ),
516
vfs_file_info_get_disp_mtime( file ) );
518
strftime( buf, sizeof( buf ),
519
time_format, localtime( vfs_file_info_get_atime( file ) ) );
520
gtk_label_set_text( GTK_LABEL( atime ), buf );
522
owner_group = (char *) vfs_file_info_get_disp_owner( file );
523
tmp = strchr( owner_group, ':' );
524
data->owner_name = g_strndup( owner_group, tmp - owner_group );
525
gtk_entry_set_text( GTK_ENTRY( data->owner ), data->owner_name );
526
data->group_name = g_strdup( tmp + 1 );
527
gtk_entry_set_text( GTK_ENTRY( data->group ), data->group_name );
529
for ( i = 0; i < N_CHMOD_ACTIONS; ++i )
531
if ( data->chmod_states[ i ] != 2 ) /* allow to touch this bit */
533
data->chmod_states[ i ] = ( vfs_file_info_get_mode( file ) & chmod_flags[ i ] ? 1 : 0 );
534
gtk_toggle_button_set_active( data->chmod_btns[ i ], data->chmod_states[ i ] );
539
if ( need_calc_size )
541
/* The total file size displayed in "File Properties" is not
542
completely calculated yet. So "Calculating..." is displayed. */
543
calculating = _( "Calculating..." );
544
gtk_label_set_text( data->total_size_label, calculating );
545
gtk_label_set_text( data->size_on_disk_label, calculating );
547
g_object_set_data( G_OBJECT( dlg ), "calc_size", data );
548
data->calc_size_thread = g_thread_create ( ( GThreadFunc ) calc_size,
550
data->update_label_timer = g_timeout_add( 250,
551
( GSourceFunc ) on_update_labels,
555
g_signal_connect( dlg, "response",
556
G_CALLBACK(on_dlg_response), dlg );
557
g_signal_connect_swapped( ptk_ui_xml_get_widget(dlg, "ok_button"),
559
G_CALLBACK(gtk_widget_destroy), dlg );
560
g_signal_connect_swapped( ptk_ui_xml_get_widget(dlg, "cancel_button"),
562
G_CALLBACK(gtk_widget_destroy), dlg );
564
ptk_dialog_fit_small_screen( dlg );
568
static uid_t uid_from_name( const char* user_name )
574
pw = getpwnam( user_name );
582
for ( p = user_name; *p; ++p )
584
if ( !g_ascii_isdigit( *p ) )
589
#if 0 /* This is not needed */
590
/* Check the existance */
591
pw = getpwuid( uid );
592
if ( !pw ) /* Invalid uid */
600
gid_t gid_from_name( const char* group_name )
606
grp = getgrnam( group_name );
614
for ( p = group_name; *p; ++p )
616
if ( !g_ascii_isdigit( *p ) )
621
#if 0 /* This is not needed */
622
/* Check the existance */
623
grp = getgrgid( gid );
624
if ( !grp ) /* Invalid gid */
633
on_dlg_response ( GtkDialog *dialog,
637
FilePropertiesDialogData * data;
642
const char* owner_name;
643
const char* group_name;
648
GtkWidget* ask_recursive;
651
data = ( FilePropertiesDialogData* ) g_object_get_data( G_OBJECT( dialog ),
655
if ( data->update_label_timer )
656
g_source_remove( data->update_label_timer );
659
if ( data->calc_size_thread )
660
g_thread_join( data->calc_size_thread );
662
if ( response_id == GTK_RESPONSE_OK )
664
GtkWidget* open_with;
666
/* Set default action for mimetype */
667
if( ( open_with = ptk_ui_xml_get_widget( GTK_WIDGET(dialog), "open_with" ) ) )
669
GtkTreeModel* model = gtk_combo_box_get_model( GTK_COMBO_BOX(open_with) );
672
if( model && gtk_combo_box_get_active_iter( GTK_COMBO_BOX(open_with), &it ) )
675
gtk_tree_model_get( model, &it, 2, &action, -1 );
678
file = ( VFSFileInfo* ) data->file_list->data;
679
VFSMimeType* mime = vfs_file_info_get_mime_type( file );
680
vfs_mime_type_set_default_action( mime, action );
681
vfs_mime_type_unref( mime );
687
/* Check if we need chown */
688
owner_name = gtk_entry_get_text( data->owner );
689
if ( owner_name && *owner_name &&
690
(!data->owner_name || strcmp( owner_name, data->owner_name )) )
692
uid = uid_from_name( owner_name );
695
ptk_show_error( GTK_WINDOW( dialog ), _("Error"), _( "Invalid User" ) );
699
group_name = gtk_entry_get_text( data->group );
700
if ( group_name && *group_name &&
701
(!data->group_name || strcmp( group_name, data->group_name )) )
703
gid = gid_from_name( group_name );
706
ptk_show_error( GTK_WINDOW( dialog ), _("Error"), _( "Invalid Group" ) );
711
for ( i = 0; i < N_CHMOD_ACTIONS; ++i )
713
if ( gtk_toggle_button_get_inconsistent( data->chmod_btns[ i ] ) )
715
data->chmod_states[ i ] = 2; /* Don't touch this bit */
717
else if ( data->chmod_states[ i ] != gtk_toggle_button_get_active( data->chmod_btns[ i ] ) )
720
data->chmod_states[ i ] = gtk_toggle_button_get_active( data->chmod_btns[ i ] );
722
else /* Don't change this bit */
724
data->chmod_states[ i ] = 2;
728
if ( uid != -1 || gid != -1 || mod_change )
731
for ( l = data->file_list; l; l = l->next )
733
file = ( VFSFileInfo* ) l->data;
734
file_path = g_build_filename( data->dir_path,
735
vfs_file_info_get_name( file ), NULL );
736
file_list = g_list_prepend( file_list, file_path );
739
task = ptk_file_task_new( VFS_FILE_TASK_CHMOD_CHOWN,
742
GTK_WINDOW(gtk_widget_get_parent( GTK_WIDGET( dialog ) )) );
744
for ( l = data->file_list; l; l = l->next )
746
file = ( VFSFileInfo* ) l->data;
747
if ( vfs_file_info_is_dir( file ) )
749
ask_recursive = gtk_message_dialog_new(
750
GTK_WINDOW( data->dlg ),
752
GTK_MESSAGE_QUESTION,
754
_( "Do you want to recursively apply these changes to all files and sub-folders?" ) );
755
ptk_file_task_set_recursive( task,
756
( GTK_RESPONSE_YES == gtk_dialog_run( GTK_DIALOG( ask_recursive ) ) ) );
757
gtk_widget_destroy( ask_recursive );
764
/* If the permissions of file has been changed by the user */
765
ptk_file_task_set_chmod( task, data->chmod_states );
768
ptk_file_task_set_chown( task, uid, gid );
769
ptk_file_task_run( task );
772
* This file list will be freed by file operation, so we don't
773
* need to do this. Just set the pointer to NULL.
775
data->file_list = NULL;
779
g_free( data->owner_name );
780
g_free( data->group_name );
782
*NOTE: File operation chmod/chown will free the list when it's done,
783
*and we only need to free it when there is no file operation applyed.
785
g_slice_free( FilePropertiesDialogData, data );
788
gtk_widget_destroy( GTK_WIDGET( dialog ) );