/****************************************************************************** * $Id$ * * Copyright (c) 2005-2007 Transmission authors and contributors * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #include #include #include #include #include #include "conf.h" #include "dialogs.h" #include "tr_cell_renderer_progress.h" #include "tr_core.h" #include "tr_icon.h" #include "tr_prefs.h" #include "util.h" #include "transmission.h" #define UPDATE_INTERVAL 1000 #define PREFNAME "transmission-dialog-pref-name" #define FILESWIND_EXTRA_INDENT 4 #define STRIPROOT( path ) \ ( g_path_is_absolute( (path) ) ? g_path_skip_root( (path) ) : (path) ) struct addcb { GtkWidget * widget; TrCore * core; gboolean autostart; gboolean usingaltdir; GtkFileChooser *altdir; GtkButtonBox *altbox; }; struct dirdata { GtkWidget * widget; TrCore * core; GList * files; enum tr_torrent_action action; gboolean paused; }; struct quitdata { callbackfunc_t func; void * cbdata; }; struct infowind { GtkWidget * widget; TrTorrent * tor; int64_t size; GtkTreeModel * model; GtkTreeRowReference * row; GtkTreeModel * filesmodel; guint timer; struct { tr_tracker_info_t * track; GtkLabel * trackwid; GtkLabel * annwid; GtkLabel * scrwid; int seed; GtkLabel * seedwid; int leech; GtkLabel * leechwid; int done; GtkLabel * donewid; uint64_t up; GtkLabel * upwid; uint64_t down; GtkLabel * downwid; uint64_t left; GtkLabel * leftwid; } inf; }; static void addwindnocore( gpointer gdata, GObject * core ); static void autoclick(GtkWidget *widget, gpointer gdata); static void dirclick(GtkWidget *widget, gpointer gdata); static void addresp(GtkWidget *widget, gint resp, gpointer gdata); static GtkWidget * makeinfotab( TrTorrent * tor, struct infowind * iw ); static void infoupdate( struct infowind * iw, int force ); static void fmtpeercount( GtkLabel * label, int count ); static void promptdirnocore( gpointer gdata, GObject * core ); static void promptresp( GtkWidget * widget, gint resp, gpointer data ); static void quitresp( GtkWidget * widget, gint resp, gpointer data ); static GtkWidget * makefilestab( TrTorrent * tor, GtkTreeModel ** modelret ); static void stylekludge( GObject * obj, GParamSpec * spec, gpointer data ); static void infowinddead( GtkWidget * widget, gpointer data ); static void infotorclosed( gpointer data, GObject * tor ); static void parsepath( GtkTreeStore * store, GtkTreeIter * ret, const char * path, int index, uint64_t size ); static uint64_t getdirtotals( GtkTreeStore * store, GtkTreeIter * parent ); static gboolean infowindupdate( gpointer data ); static float updateprogress( GtkTreeModel * model, GtkTreeIter * parent, uint64_t total, float * progress ); void makeaddwind( GtkWindow * parent, TrCore * core ) { GtkWidget *wind = gtk_file_chooser_dialog_new(_("Add a Torrent"), parent, GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); struct addcb *data = g_new(struct addcb, 1); GtkWidget *vbox = gtk_vbox_new(FALSE, 3); GtkWidget *bbox = gtk_hbutton_box_new(); GtkWidget *autocheck = gtk_check_button_new_with_mnemonic( _("Automatically _start torrent")); GtkWidget *dircheck = gtk_check_button_new_with_mnemonic( _("Use alternate _download directory")); GtkFileFilter *filter = gtk_file_filter_new(); GtkFileFilter *unfilter = gtk_file_filter_new(); GtkWidget *getdir = gtk_file_chooser_button_new( _("Choose a download directory"), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); const char * pref; data->widget = wind; data->core = core; data->autostart = TRUE; data->usingaltdir = FALSE; data->altdir = GTK_FILE_CHOOSER(getdir); data->altbox = GTK_BUTTON_BOX(bbox); g_object_weak_ref( G_OBJECT( core ), addwindnocore, data ); gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_EDGE); gtk_box_pack_start_defaults(GTK_BOX(bbox), dircheck); gtk_box_pack_start_defaults(GTK_BOX(bbox), getdir); gtk_box_pack_start_defaults(GTK_BOX(vbox), autocheck); gtk_box_pack_start_defaults(GTK_BOX(vbox), bbox); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(autocheck), TRUE); pref = tr_prefs_get( PREF_ID_DIR ); if( NULL != pref ) { gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( wind ), pref ); } gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dircheck), FALSE); gtk_widget_set_sensitive(getdir, FALSE); gtk_file_filter_set_name(filter, _("Torrent files")); gtk_file_filter_add_pattern(filter, "*.torrent"); gtk_file_filter_set_name(unfilter, _("All files")); gtk_file_filter_add_pattern(unfilter, "*"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(wind), filter); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(wind), unfilter); gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(wind), TRUE); gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(wind), vbox); g_signal_connect(G_OBJECT(autocheck), "clicked", G_CALLBACK(autoclick),data); g_signal_connect(G_OBJECT(dircheck), "clicked", G_CALLBACK(dirclick), data); g_signal_connect(G_OBJECT(wind), "response", G_CALLBACK(addresp), data); gtk_widget_show_all(wind); } void addwindnocore( gpointer gdata, GObject * core SHUTUP ) { struct addcb * data = gdata; /* prevent the response callback from trying to remove the weak reference which no longer exists */ data->core = NULL; gtk_dialog_response( GTK_DIALOG( data->widget ), GTK_RESPONSE_NONE ); } static void autoclick(GtkWidget *widget, gpointer gdata) { struct addcb *data = gdata; data->autostart = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); } static void dirclick(GtkWidget *widget, gpointer gdata) { struct addcb *data = gdata; data->usingaltdir = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); gtk_widget_set_sensitive(GTK_WIDGET(data->altdir), data->usingaltdir); } static void addresp(GtkWidget *widget, gint resp, gpointer gdata) { struct addcb *data = gdata; GSList *files, *ii; GList *stupidgtk; char *dir; enum tr_torrent_action action; if(GTK_RESPONSE_ACCEPT == resp) { dir = NULL; if(data->usingaltdir) dir = gtk_file_chooser_get_filename(data->altdir); files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(widget)); action = toraddaction( tr_prefs_get( PREF_ID_ADDSTD ) ); if( NULL == dir ) { stupidgtk = NULL; for( ii = files; NULL != ii; ii = ii->next ) { stupidgtk = g_list_append( stupidgtk, ii->data ); } tr_core_add_list( data->core, stupidgtk, action, !data->autostart ); freestrlist(stupidgtk); } else { for( ii = files; NULL != ii; ii = ii->next ) { tr_core_add_dir( data->core, ii->data, dir, action, !data->autostart ); g_free( ii->data ); } g_free( dir ); } tr_core_torrents_added( data->core ); g_slist_free(files); } if( NULL != data->core ) { g_object_weak_unref( G_OBJECT( data->core ), addwindnocore, data ); } g_free( data ); gtk_widget_destroy(widget); } void makeinfowind( GtkWindow * parent, GtkTreeModel * model, GtkTreePath * path, TrTorrent * tor ) { struct infowind * iw; GtkWidget * wind, * box, * label, * sep, * tabs, * page; tr_info_t * inf; char * name, * size; iw = g_new0( struct infowind, 1 ); inf = tr_torrent_info( tor ); name = g_strdup_printf( _("%s - Properties for %s"), g_get_application_name(), inf->name ); wind = gtk_dialog_new_with_buttons( name, parent, GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL ); g_free( name ); gtk_dialog_set_default_response( GTK_DIALOG( wind ), GTK_RESPONSE_ACCEPT ); gtk_window_set_resizable( GTK_WINDOW( wind ), TRUE ); gtk_window_set_role( GTK_WINDOW( wind ), "tr-info" ); gtk_widget_set_name( wind, "TransmissionDialog" ); box = GTK_DIALOG( wind )->vbox; /* add label with file name and size */ label = gtk_label_new( NULL ); size = readablesize( inf->totalSize ); name = g_markup_printf_escaped( "%s (%s)", inf->name, size ); free( size ); gtk_label_set_markup( GTK_LABEL( label ), name ); g_free( name ); gtk_label_set_selectable( GTK_LABEL( label ), TRUE ); gtk_widget_show( label ); gtk_box_pack_start( GTK_BOX( box ), label, FALSE, FALSE, 6 ); /* add separator */ sep = gtk_hseparator_new(); gtk_widget_show( sep ); gtk_box_pack_start( GTK_BOX( box ), sep, FALSE, FALSE, 6 ); /* add tab bar */ tabs = gtk_notebook_new(); gtk_widget_show( tabs ); gtk_box_pack_start( GTK_BOX( box ), tabs, TRUE, TRUE, 6 ); /* add general tab */ label = gtk_label_new( _("General") ); gtk_widget_show( label ); page = makeinfotab( tor, iw ); gtk_notebook_append_page( GTK_NOTEBOOK( tabs ), page, label ); /* add files tab */ label = gtk_label_new( _("Files") ); gtk_widget_show( label ); /* XXX should use sizingmagic() here */ page = makefilestab( tor, &iw->filesmodel ); gtk_notebook_append_page( GTK_NOTEBOOK( tabs ), page, label ); /* set up the callback data */ iw->widget = wind; iw->tor = tor; iw->size = inf->totalSize; iw->model = model; iw->row = gtk_tree_row_reference_new( model, path ); iw->timer = g_timeout_add( UPDATE_INTERVAL, infowindupdate, iw ); g_object_ref( model ); g_object_weak_ref( G_OBJECT( tor ), infotorclosed, iw ); g_signal_connect( wind, "destroy", G_CALLBACK( infowinddead ), iw ); g_signal_connect( wind, "response", G_CALLBACK( gtk_widget_destroy ), 0 ); infoupdate( iw, 1 ); infowindupdate( iw ); gtk_widget_show( wind ); } #define INFOLINE( tab, ii, nam, val ) \ do \ { \ char * txt = g_markup_printf_escaped( "%s", (nam) ); \ GtkWidget * wid = gtk_label_new( NULL ); \ gtk_misc_set_alignment( GTK_MISC( wid ), 0, .5 ); \ gtk_label_set_markup( GTK_LABEL( wid ), txt ); \ gtk_table_attach( GTK_TABLE( (tab) ), wid, 0, 1, (ii), (ii) + 1, \ GTK_FILL, GTK_FILL, 0, 0 ); \ gtk_label_set_selectable( GTK_LABEL( (val) ), TRUE ); \ gtk_misc_set_alignment( GTK_MISC( (val) ), 0, .5 ); \ gtk_table_attach( GTK_TABLE( (tab) ), (val), 1, 2, (ii), (ii) + 1, \ GTK_FILL, GTK_FILL, 0, 0); \ (ii)++; \ g_free( txt ); \ } while( 0 ) #define INFOLINEF( tab, ii, fmt, nam, val ) \ do \ { \ char * buf = g_strdup_printf( fmt, val ); \ GtkWidget * lwid = gtk_label_new( buf ); \ g_free( buf ); \ INFOLINE( tab, ii, nam, lwid ); \ } while( 0 ) #define INFOLINEW( tab, ii, nam, val ) \ do \ { \ GtkWidget * lwid = gtk_label_new( (val) ); \ INFOLINE( (tab), (ii), (nam), lwid ); \ } while( 0 ) #define INFOLINEA( tab, ii, nam, val ) \ do \ { \ GtkWidget * lwid = gtk_label_new( (val) ); \ g_free( val ); \ INFOLINE( (tab), (ii), (nam), lwid ); \ } while( 0 ) #define INFOLINEU( tab, ii, nam, stor ) \ do \ { \ GtkWidget * lwid = gtk_label_new( NULL ); \ (stor) = GTK_LABEL( lwid ); \ INFOLINE( (tab), (ii), (nam), lwid); \ } while( 0 ) #define INFOSEP( tab, ii ) \ do \ { \ GtkWidget * wid = gtk_hseparator_new(); \ gtk_table_attach( GTK_TABLE( (tab) ), wid, 0, 2, (ii), (ii) + 1, \ GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0 ); \ (ii)++; \ } while( 0 ) GtkWidget * makeinfotab( TrTorrent * tor, struct infowind * iw ) { const int rowcount = 17; tr_info_t * inf; int ii; GtkWidget * table; inf = tr_torrent_info( tor ); table = gtk_table_new( rowcount, 2, FALSE ); gtk_table_set_col_spacings( GTK_TABLE( table ), 12 ); gtk_table_set_row_spacings( GTK_TABLE( table ), 12 ); gtk_container_set_border_width( GTK_CONTAINER( table ), 6 ); ii = 0; INFOLINEU( table, ii, _("Tracker:"), iw->inf.trackwid ); INFOLINEU( table, ii, _("Announce:"), iw->inf.annwid ); INFOLINEU( table, ii, _("Scrape:"), iw->inf.scrwid ); INFOSEP( table, ii ); INFOLINEW( table, ii, _("Info Hash:"), inf->hashString ); INFOLINEA( table, ii, _("Piece Size:"), readablesize( inf->pieceSize ) ); INFOLINEF( table, ii, "%i", _("Pieces:"), inf->pieceCount ); INFOLINEA( table, ii, _("Total Size:"), readablesize( inf->totalSize ) ); INFOSEP( table, ii ); INFOLINEU( table, ii, _("Seeders:"), iw->inf.seedwid ); INFOLINEU( table, ii, _("Leechers:"), iw->inf.leechwid ); INFOLINEU( table, ii, _("Completed:"), iw->inf.donewid ); INFOSEP( table, ii ); INFOLINEW( table, ii, _("Directory:"), tr_torrentGetFolder( tr_torrent_handle( tor ) ) ); INFOLINEU( table, ii, _("Downloaded:"), iw->inf.downwid ); INFOLINEU( table, ii, _("Uploaded:"), iw->inf.upwid ); INFOLINEU( table, ii, _("Remaining:"), iw->inf.leftwid ); g_assert( rowcount == ii ); gtk_widget_show_all( table ); return table; } void infoupdate( struct infowind * iw, int force ) { int seed, leech, done; uint64_t up, down, left; tr_tracker_info_t * track; GtkTreePath * path; GtkTreeIter iter; char * str; path = gtk_tree_row_reference_get_path( iw->row ); if( NULL == path || !gtk_tree_model_get_iter( iw->model, &iter, path ) ) { g_free( path ); return; } g_free( path ); gtk_tree_model_get( iw->model, &iter, MC_TRACKER, &track, MC_SEED, &seed, MC_LEECH, &leech, MC_DONE, &done, MC_DOWN, &down, MC_UP, &up, MC_LEFT, &left, -1 ); if( track != iw->inf.track || force ) { if( 80 == track->port ) { str = g_strdup_printf( "http://%s", track->address ); } else { str = g_strdup_printf( "http://%s:%i", track->address, track->port ); } gtk_label_set_text( iw->inf.trackwid, str ); g_free( str ); gtk_label_set_text( iw->inf.annwid, track->announce ); gtk_label_set_text( iw->inf.scrwid, track->scrape ); } if( seed != iw->inf.seed || force ) { fmtpeercount( iw->inf.seedwid, seed ); iw->inf.seed = seed; } if( leech != iw->inf.leech || force ) { fmtpeercount( iw->inf.leechwid, leech ); iw->inf.leech = leech; } if( done != iw->inf.done || force ) { fmtpeercount( iw->inf.donewid, done ); iw->inf.done = done; } if( down != iw->inf.down || force ) { str = readablesize( down ); gtk_label_set_text( iw->inf.downwid, str ); g_free( str ); iw->inf.down = down; } if( up != iw->inf.up || force ) { str = readablesize( up ); gtk_label_set_text( iw->inf.upwid, str ); g_free( str ); iw->inf.up = up; } if( left != iw->inf.left || force ) { str = readablesize( left ); gtk_label_set_text( iw->inf.leftwid, str ); g_free( str ); iw->inf.left = left; } } void fmtpeercount( GtkLabel * label, int count ) { char str[16]; if( 0 > count ) { gtk_label_set_text( label, _("?") ); } else { snprintf( str, sizeof str, "%i", count ); gtk_label_set_text( label, str ); } } void promptfordir( GtkWindow * parent, TrCore * core, GList * files, enum tr_torrent_action act, gboolean paused ) { struct dirdata * stuff; GtkWidget * wind; stuff = g_new( struct dirdata, 1 ); stuff->core = core; stuff->files = dupstrlist( files ); stuff->action = act; stuff->paused = paused; g_object_weak_ref( G_OBJECT( core ), promptdirnocore, stuff ); wind = gtk_file_chooser_dialog_new( _("Choose a directory"), parent, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL ); gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER( wind ), TRUE ); gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER( wind ), FALSE ); gtk_file_chooser_set_filename( GTK_FILE_CHOOSER( wind ), getdownloaddir() ); stuff->widget = wind; g_signal_connect( G_OBJECT( wind ), "response", G_CALLBACK( promptresp ), stuff ); gtk_widget_show_all(wind); } void promptdirnocore( gpointer gdata, GObject * core SHUTUP ) { struct dirdata * stuff = gdata; /* prevent the response callback from trying to remove the weak reference which no longer exists */ stuff->core = NULL; gtk_dialog_response( GTK_DIALOG( stuff->widget ), GTK_RESPONSE_NONE ); } static void promptresp( GtkWidget * widget, gint resp, gpointer data ) { struct dirdata * stuff; char * dir; GList * ii; stuff = data; if( GTK_RESPONSE_ACCEPT == resp ) { dir = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( widget ) ); /* it seems that we will always get a directory */ g_assert( NULL != dir ); for( ii = g_list_first( stuff->files ); NULL != ii; ii = ii->next ) { tr_core_add_dir( stuff->core, ii->data, dir, stuff->action, stuff->paused ); } tr_core_torrents_added( stuff->core ); g_free( dir ); } if( NULL != stuff->core ) { g_object_weak_unref( G_OBJECT( stuff->core ), promptdirnocore, stuff ); } freestrlist( stuff->files ); g_free( stuff ); gtk_widget_destroy( widget ); } void askquit( GtkWindow * parent, callbackfunc_t func, void * cbdata ) { struct quitdata * stuff; GtkWidget * wind; if( !tr_prefs_get_bool_with_default( PREF_ID_ASKQUIT ) ) { func( cbdata ); return; } stuff = g_new( struct quitdata, 1 ); stuff->func = func; stuff->cbdata = cbdata; wind = gtk_message_dialog_new( parent, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("Are you sure you want to quit %s?"), g_get_application_name() ); gtk_dialog_set_default_response( GTK_DIALOG( wind ), GTK_RESPONSE_YES ); g_signal_connect( G_OBJECT( wind ), "response", G_CALLBACK( quitresp ), stuff ); gtk_widget_show_all( wind ); } static void quitresp( GtkWidget * widget, gint resp, gpointer data ) { struct quitdata * stuff; stuff = data; if( GTK_RESPONSE_YES == resp ) { stuff->func( stuff->cbdata ); } g_free( stuff ); gtk_widget_destroy( widget ); } enum filescols { FC_STOCK = 0, FC_LABEL, FC_PROG, FC_KEY, FC_INDEX, FC_SIZE, FC__MAX }; GtkWidget * makefilestab( TrTorrent * tor, GtkTreeModel ** modelret ) { GType cols[] = { G_TYPE_STRING, G_TYPE_STRING, G_TYPE_FLOAT, G_TYPE_STRING, G_TYPE_INT, G_TYPE_UINT64 }; tr_info_t * inf; GtkTreeStore * store; int ii; GtkWidget * view, * scroll, * frame; GtkCellRenderer * rend; GtkTreeViewColumn * col; GtkTreeSelection * sel; char * label; g_assert( ALEN( cols ) == FC__MAX ); /* set up the model */ inf = tr_torrent_info( tor ); store = gtk_tree_store_newv( FC__MAX, cols ); *modelret = GTK_TREE_MODEL( store ); for( ii = 0; ii < inf->fileCount; ii++ ) { parsepath( store, NULL, STRIPROOT( inf->files[ii].name ), ii, inf->files[ii].length ); } getdirtotals( store, NULL ); gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( store ), FC_KEY, GTK_SORT_ASCENDING ); /* create the view */ view = gtk_tree_view_new_with_model( GTK_TREE_MODEL( store ) ); /* add file column */ col = gtk_tree_view_column_new(); gtk_tree_view_column_set_expand( col, TRUE ); gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_AUTOSIZE ); gtk_tree_view_column_set_title( col, _("File") ); /* add icon renderer */ rend = gtk_cell_renderer_pixbuf_new(); gtk_tree_view_column_pack_start( col, rend, FALSE ); gtk_tree_view_column_add_attribute( col, rend, "stock-id", FC_STOCK ); /* add text renderer */ rend = gtk_cell_renderer_text_new(); g_object_set( rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL ); gtk_tree_view_column_pack_start( col, rend, TRUE ); gtk_tree_view_column_add_attribute( col, rend, "markup", FC_LABEL ); gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col ); /* add progress column */ col = gtk_tree_view_column_new(); gtk_tree_view_column_set_title( col, _("Progress") ); rend = tr_cell_renderer_progress_new(); /* this string is only used to determine the size of the progress bar */ label = g_markup_printf_escaped( "%s", _(" fnord fnord ") ); g_object_set( rend, "show-text", FALSE, "bar-sizing", label, NULL ); g_free( label ); gtk_tree_view_column_pack_start( col, rend, FALSE ); gtk_tree_view_column_add_attribute( col, rend, "progress", FC_PROG ); gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col ); /* XXX this shouldn't be necessary */ g_signal_connect( view, "notify::style", G_CALLBACK( stylekludge ), rend ); /* set up view */ sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) ); gtk_tree_selection_set_mode( sel, GTK_SELECTION_NONE ); gtk_tree_view_expand_all( GTK_TREE_VIEW( view ) ); gtk_tree_view_set_search_column( GTK_TREE_VIEW( view ), FC_LABEL ); gtk_widget_show( view ); /* create the scrolled window and stick the view in it */ scroll = gtk_scrolled_window_new( NULL, NULL ); gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scroll ), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC ); gtk_container_add( GTK_CONTAINER( scroll ), view ); gtk_widget_show( scroll ); /* create a frame around the scroll to make it look a little better */ frame = gtk_frame_new( NULL ); gtk_frame_set_shadow_type( GTK_FRAME( frame ), GTK_SHADOW_IN ); gtk_container_add( GTK_CONTAINER( frame ), scroll ); gtk_widget_show( frame ); return frame; } /* kludge to have the progress bars notice theme changes */ static void stylekludge( GObject * obj, GParamSpec * spec, gpointer data ) { TrCellRendererProgress * rend = TR_CELL_RENDERER_PROGRESS( data ); if( 0 == strcmp( "style", spec->name ) ) { tr_cell_renderer_progress_reset_style( rend ); gtk_widget_queue_draw( GTK_WIDGET( obj ) ); } } static void infowinddead( GtkWidget * widget SHUTUP, gpointer data ) { struct infowind * iw = data; if( NULL != iw->widget ) { g_object_weak_unref( G_OBJECT( iw->tor ), infotorclosed, iw ); infotorclosed( iw, G_OBJECT( iw->tor ) ); } } static void infotorclosed( gpointer data, GObject * tor SHUTUP ) { struct infowind * iw = data; GtkWidget * widget; /* neuter the widget's destroy callback */ widget = iw->widget; iw->widget = NULL; g_source_remove( iw->timer ); g_object_unref( iw->filesmodel ); g_object_unref( iw->model ); gtk_tree_row_reference_free( iw->row ); gtk_widget_destroy( widget ); g_free( iw ); } static void parsepath( GtkTreeStore * store, GtkTreeIter * ret, const char * path, int index, uint64_t size ) { GtkTreeModel * model; GtkTreeIter * parent, start, iter; char * file, * dir, * lower, * mykey, * modelkey; const char * stock; model = GTK_TREE_MODEL( store ); parent = NULL; file = g_path_get_basename( path ); if( 0 != strcmp( file, path ) ) { dir = g_path_get_dirname( path ); parsepath( store, &start, dir, index, size ); g_free( dir ); parent = &start; } lower = g_utf8_casefold( file, -1 ); mykey = g_utf8_collate_key( lower, -1 ); if( gtk_tree_model_iter_children( model, &iter, parent ) ) { do { gtk_tree_model_get( model, &iter, FC_KEY, &modelkey, -1 ); if( NULL != modelkey && 0 == strcmp( mykey, modelkey ) ) { goto done; } } while( gtk_tree_model_iter_next( model, &iter ) ); } gtk_tree_store_append( store, &iter, parent ); if( NULL == ret ) { stock = GTK_STOCK_FILE; } else { stock = GTK_STOCK_DIRECTORY; size = 0; index = -1; } gtk_tree_store_set( store, &iter, FC_INDEX, index, FC_LABEL, file, FC_KEY, mykey, FC_STOCK, stock, FC_SIZE, size, -1 ); done: g_free( mykey ); g_free( lower ); g_free( file ); if( NULL != ret ) { memcpy( ret, &iter, sizeof( iter ) ); } } static uint64_t getdirtotals( GtkTreeStore * store, GtkTreeIter * parent ) { GtkTreeModel * model; GtkTreeIter iter; uint64_t mysize, subsize; char * sizestr, * name, * label; model = GTK_TREE_MODEL( store ); mysize = 0; if( gtk_tree_model_iter_children( model, &iter, parent ) ) { do { if( gtk_tree_model_iter_has_child( model, &iter ) ) { subsize = getdirtotals( store, &iter ); gtk_tree_store_set( store, &iter, FC_SIZE, subsize, -1 ); } else { gtk_tree_model_get( model, &iter, FC_SIZE, &subsize, -1 ); } gtk_tree_model_get( model, &iter, FC_LABEL, &name, -1 ); sizestr = readablesize( subsize ); label = g_markup_printf_escaped( "%s (%s)", name, sizestr ); g_free( sizestr ); g_free( name ); gtk_tree_store_set( store, &iter, FC_LABEL, label, -1 ); g_free( label ); mysize += subsize; } while( gtk_tree_model_iter_next( model, &iter ) ); } return mysize; } static gboolean infowindupdate( gpointer data ) { struct infowind * iw; float * progress; iw = data; progress = tr_torrentCompletion( tr_torrent_handle( iw->tor ) ); updateprogress( iw->filesmodel, NULL, iw->size, progress ); free( progress ); infoupdate( iw, 0 ); return TRUE; } static float updateprogress( GtkTreeModel * model, GtkTreeIter * parent, uint64_t total, float * progress ) { GtkTreeIter iter; float myprog, subprog; int index; uint64_t size; myprog = 0.0; if( gtk_tree_model_iter_children( model, &iter, parent ) ) { do { if( gtk_tree_model_iter_has_child( model, &iter ) ) { gtk_tree_model_get( model, &iter, FC_SIZE, &size, -1 ); subprog = updateprogress( model, &iter, size, progress ); } else { gtk_tree_model_get( model, &iter, FC_SIZE, &size, FC_INDEX, &index, -1 ); g_assert( 0 <= index ); subprog = progress[index]; } gtk_tree_store_set( GTK_TREE_STORE( model ), &iter, FC_PROG, subprog, -1 ); myprog += subprog * ( total ? ( float )size / ( float )total : 1 ); } while( gtk_tree_model_iter_next( model, &iter ) ); } return myprog; }