diff --git a/gtk/conf.c b/gtk/conf.c index fad40d5ad..e575aac18 100644 --- a/gtk/conf.c +++ b/gtk/conf.c @@ -1,7 +1,7 @@ /****************************************************************************** * $Id$ * - * Copyright (c) 2005-2006 Transmission authors and contributors + * 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"), @@ -500,7 +500,11 @@ cf_savestate(benc_val_t *state, char **errstr) { } void -cf_freestate(benc_val_t *state) { - tr_bencFree(state); - g_free(state); +cf_freestate( benc_val_t * state ) +{ + if( NULL != state ) + { + tr_bencFree( state ); + g_free( state ); + } } diff --git a/gtk/conf.h b/gtk/conf.h index 32c4edab8..58860b248 100644 --- a/gtk/conf.h +++ b/gtk/conf.h @@ -49,17 +49,4 @@ cf_savestate(benc_val_t *state, char **errstr); void cf_freestate(benc_val_t *state); -/* macros for names of prefs we use */ -#define PREF_PORT "listening-port" -#define PREF_USEDOWNLIMIT "use-download-limit" -#define PREF_DOWNLIMIT "download-limit" -#define PREF_USEUPLIMIT "use-upload-limit" -#define PREF_UPLIMIT "upload-limit" -#define PREF_DIR "download-directory" -#define PREF_ASKDIR "ask-download-directory" -#define PREF_ADDSTD "add-behavior-standard" -#define PREF_ADDIPC "add-behavior-ipc" -#define PREF_MSGLEVEL "message-level" -#define PREF_NAT "use-nat-traversal" - #endif /* TG_CONF_H */ diff --git a/gtk/dialogs.c b/gtk/dialogs.c index a99fa3dd9..0b5ae5c21 100644 --- a/gtk/dialogs.c +++ b/gtk/dialogs.c @@ -1,7 +1,7 @@ /****************************************************************************** * $Id$ * - * Copyright (c) 2005-2006 Transmission authors and contributors + * 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"), @@ -31,26 +31,14 @@ #include "conf.h" #include "dialogs.h" -#include "transmission.h" +#include "tr_icon.h" +#include "tr_prefs.h" #include "util.h" +#include "transmission.h" + #define PREFNAME "transmission-dialog-pref-name" -/* default values for a couple prefs */ -#define DEF_DOWNLIMIT 100 -#define DEF_USEDOWNLIMIT FALSE -#define DEF_UPLIMIT 20 -#define DEF_USEUPLIMIT TRUE -#define DEF_ASKDIR FALSE -#define DEF_NAT TRUE - -struct prefdata { - GList *prefwidgets; - GtkWindow *parent; - TrBackend *back; - GtkTooltips * tips; -}; - struct addcb { add_torrents_func_t addfunc; void *data; @@ -68,12 +56,12 @@ struct dirdata guint flags; }; -static void -clicklimitbox(GtkWidget *widget, gpointer gdata); -static void -freedata(gpointer gdata, GClosure *closure); -static void -clickdialog(GtkWidget *widget, int resp, gpointer gdata); +struct quitdata +{ + callbackfunc_t func; + void * cbdata; +}; + static void autoclick(GtkWidget *widget, gpointer gdata); static void @@ -82,401 +70,8 @@ static void addresp(GtkWidget *widget, gint resp, gpointer gdata); static void promptresp( GtkWidget * widget, gint resp, gpointer data ); - static void -setupprefwidget(GtkWidget *widget, const char *prefname, ...) { - const char *pref = cf_getpref(prefname); - GtkTreeModel *model; - GtkTreeIter iter; - guint prefsflag, modelflag; - va_list ap; - - g_object_set_data_full(G_OBJECT(widget), PREFNAME, - g_strdup(prefname), (GDestroyNotify)g_free); - - va_start(ap, prefname); - - if(ISA(widget, GTK_TYPE_FILE_CHOOSER)) { - if(NULL != pref) - gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(widget), pref); - } - else if(ISA(widget, GTK_TYPE_SPIN_BUTTON)) - gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), - (NULL == pref ? va_arg(ap, long) : strtol(pref, NULL, 10))); - else if(ISA(widget, GTK_TYPE_TOGGLE_BUTTON)) - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), - (NULL == pref ? va_arg(ap, gboolean) : strbool(pref))); - else if(ISA(widget, GTK_TYPE_COMBO_BOX)) { - model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget)); - prefsflag = addactionflag(pref); - if(gtk_tree_model_get_iter_first(model, &iter)) - do { - gtk_tree_model_get(model, &iter, 1, &modelflag, -1); - if(modelflag == prefsflag) { - gtk_combo_box_set_active_iter(GTK_COMBO_BOX(widget), &iter); - break; - } - } while(gtk_tree_model_iter_next(model, &iter)); - } - else { - g_assert_not_reached(); - } - - va_end(ap); -} - -static void -saveprefwidget(GtkWindow *parent, GtkWidget *widget) { - char *prefname; - const char *strval; - char *freeablestr; - GtkTreeModel *model; - GtkTreeIter iter; - guint uintval; - - strval = NULL; - freeablestr = NULL; - if(ISA(widget, GTK_TYPE_FILE_CHOOSER)) { - strval = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(widget)); - if(NULL != strval) { - if(!mkdir_p(strval, 0777)) { - errmsg(parent, _("Failed to create the directory %s:\n%s"), - strval, strerror(errno)); - return; - } - } - } - else if(ISA(widget, GTK_TYPE_SPIN_BUTTON)) - freeablestr = g_strdup_printf("%i", - gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget))); - else if(ISA(widget, GTK_TYPE_TOGGLE_BUTTON)) - strval = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ? - "yes" : "no"); - else if(ISA(widget, GTK_TYPE_COMBO_BOX)) { - if(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter)) { - model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget)); - gtk_tree_model_get(model, &iter, 1, &uintval, -1); - strval = addactionname(uintval); - } - } - else { - g_assert_not_reached(); - return; - } - - prefname = g_object_get_data(G_OBJECT(widget), PREFNAME); - g_assert(NULL != prefname); - - if(NULL != strval) - cf_setpref(prefname, strval); - else if(NULL != freeablestr) { - cf_setpref(prefname, freeablestr); - g_free(freeablestr); - } -} - -/* wrap a widget in an event box with a tooltip */ -static GtkWidget * -tipbox( GtkWidget * widget, GtkTooltips * tips, const char * tip ) -{ - GtkWidget * box; - - box = gtk_event_box_new(); - gtk_container_add( GTK_CONTAINER( box ), widget ); - gtk_tooltips_set_tip( tips, box, tip, "" ); - - return box; -} - -GtkWidget * -makeprefwindow(GtkWindow *parent, TrBackend *back) { - char *title = g_strdup_printf(_("%s Preferences"), g_get_application_name()); - GtkWidget *wind = gtk_dialog_new_with_buttons(title, parent, - GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR, - GTK_STOCK_APPLY, GTK_RESPONSE_APPLY, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); - const unsigned int rowcount = 10; - GtkWidget *table = gtk_table_new(rowcount, 2, FALSE); - GtkWidget *portnum = gtk_spin_button_new_with_range(1, 0xffff, 1); - GtkWidget *natcheck = gtk_check_button_new_with_mnemonic( - _("Au_tomatic port mapping via NAT-PMP or UPnP")); - GtkWidget *askdir = gtk_check_button_new_with_mnemonic( - _("Al_ways prompt for download directory")); - GtkWidget *dirstr = gtk_file_chooser_button_new( - _("Choose a download directory"), - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); - GtkWidget *addstd = gtk_combo_box_new(); - GtkWidget *addipc = gtk_combo_box_new(); - GtkWidget *label; - GtkWidget **array; - struct prefdata *data = g_new0(struct prefdata, 1); - struct { GtkWidget *on; GtkWidget *num; GtkWidget *label; gboolean defuse; - const char *usepref; const char *numpref; long def; - const char *ontip; const char *numtip; } lim[] = { - { gtk_check_button_new_with_mnemonic(_("_Limit download speed")), - gtk_spin_button_new_with_range(0, G_MAXLONG, 1), - gtk_label_new_with_mnemonic(_("Maximum _download speed:")), - DEF_USEDOWNLIMIT, PREF_USEDOWNLIMIT, PREF_DOWNLIMIT, DEF_DOWNLIMIT, - N_("Restrict the download rate"), - N_("Speed in KiB/sec for restricted download rate")}, - { gtk_check_button_new_with_mnemonic(_("Li_mit upload speed")), - gtk_spin_button_new_with_range(0, G_MAXLONG, 1), - gtk_label_new_with_mnemonic(_("Maximum _upload speed:")), - DEF_USEUPLIMIT, PREF_USEUPLIMIT, PREF_UPLIMIT, DEF_UPLIMIT, - N_("Restrict the upload rate"), - N_("Speed in KiB/sec for restricted upload rate")}, - }; - unsigned int ii; - GtkTreeModel *model; - GtkTreeIter iter; - GtkCellRenderer *rend; - gboolean boolval; - int intval; - GtkTooltips * tips; - GtkWidget * event; - - g_free(title); - gtk_widget_set_name(wind, "TransmissionDialog"); - gtk_table_set_col_spacings(GTK_TABLE(table), 8); - gtk_table_set_row_spacings(GTK_TABLE(table), 8); - gtk_dialog_set_default_response(GTK_DIALOG(wind), GTK_RESPONSE_OK); - gtk_container_set_border_width(GTK_CONTAINER(table), 6); - gtk_window_set_resizable(GTK_WINDOW(wind), FALSE); - - tips = gtk_tooltips_new(); - g_object_ref( tips ); - gtk_object_sink( GTK_OBJECT( tips ) ); - gtk_tooltips_enable( tips ); - - data->prefwidgets = makeglist(portnum, lim[0].on, lim[0].num, lim[1].on, - lim[1].num, askdir, dirstr, addstd, addipc, natcheck, NULL); - data->parent = parent; - data->back = back; - data->tips = tips; - g_object_ref(G_OBJECT(back)); - -#define RN(n) (n), (n) + 1 - - for(ii = 0; ii < ALEN(lim); ii++) { - /* limit checkbox */ - setupprefwidget(lim[ii].on, lim[ii].usepref, (gboolean)lim[ii].defuse); - array = g_new(GtkWidget*, 3); - g_signal_connect_data(lim[ii].on, "clicked", G_CALLBACK(clicklimitbox), - array, (GClosureNotify)g_free, 0); - gtk_table_attach_defaults(GTK_TABLE(table), lim[ii].on, 0, 2, RN(ii*2)); - gtk_tooltips_set_tip( tips, lim[ii].on, gettext( lim[ii].ontip ), "" ); - - /* limit label and entry */ - gtk_label_set_mnemonic_widget(GTK_LABEL(lim[ii].label), lim[ii].num); - gtk_misc_set_alignment(GTK_MISC(lim[ii].label), 0, .5); - gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(lim[ii].num), TRUE); - setupprefwidget(lim[ii].num, lim[ii].numpref, (long)lim[ii].def); - event = tipbox( lim[ii].label, tips, gettext( lim[ii].numtip ) ); - gtk_table_attach_defaults(GTK_TABLE(table), event, 0,1,RN(ii*2+1)); - gtk_table_attach_defaults(GTK_TABLE(table), lim[ii].num, 1,2,RN(ii*2+1)); - array[0] = lim[ii].label; - array[1] = lim[ii].num; - array[2] = GINT_TO_POINTER( TRUE ); - clicklimitbox(lim[ii].on, array); - gtk_tooltips_set_tip( tips, lim[ii].num, gettext( lim[ii].numtip ), "" ); - } - ii *= 2; - - /* always ask for download dir */ - setupprefwidget( askdir, PREF_ASKDIR, ( gboolean )DEF_ASKDIR ); - array = g_new( GtkWidget *, 3 ); - g_signal_connect_data( askdir, "clicked", G_CALLBACK( clicklimitbox ), - array, ( GClosureNotify )g_free, 0 ); - gtk_table_attach_defaults(GTK_TABLE(table), askdir, 0, 2, RN(ii)); - gtk_tooltips_set_tip( tips, askdir, - _("When adding a torrent, always prompt for a directory to download data files into"), "" ); - ii++; - - /* directory label and chooser */ - label = gtk_label_new_with_mnemonic(_("Download di_rectory:")); - gtk_label_set_mnemonic_widget(GTK_LABEL(label), dirstr); - gtk_misc_set_alignment(GTK_MISC(label), 0, .5); - setupprefwidget(dirstr, PREF_DIR); - event = tipbox( label, tips, - _("Destination directory for downloaded data files") ); - gtk_table_attach_defaults(GTK_TABLE(table), event, 0, 1, RN(ii)); - event = tipbox( dirstr, tips, - _("Destination directory for downloaded data files") ); - gtk_table_attach_defaults(GTK_TABLE(table), event, 1, 2, RN(ii)); - array[0] = label; - array[1] = dirstr; - array[2] = GINT_TO_POINTER( FALSE ); - clicklimitbox( askdir, array ); - ii++; - - /* port label and entry */ - label = gtk_label_new_with_mnemonic(_("Listening _port:")); - gtk_label_set_mnemonic_widget(GTK_LABEL(label), portnum); - gtk_misc_set_alignment(GTK_MISC(label), 0, .5); - gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(portnum), TRUE); - setupprefwidget(portnum, PREF_PORT, (long)TR_DEFAULT_PORT); - event = tipbox( label, tips, - _("TCP port number to listen for peer connections") ); - gtk_table_attach_defaults(GTK_TABLE(table), event, 0, 1, RN(ii)); - gtk_table_attach_defaults(GTK_TABLE(table), portnum, 1, 2, RN(ii)); - gtk_tooltips_set_tip( tips, portnum, - _("TCP port number to listen for peer connections"), "" ); - ii++; - - /* NAT traversal checkbox */ - intval = tr_handleStatus(tr_backend_handle(back))->natTraversalStatus; - boolval = !TR_NAT_TRAVERSAL_IS_DISABLED( intval ); - setupprefwidget(natcheck, PREF_NAT, boolval); - gtk_table_attach_defaults(GTK_TABLE(table), natcheck, 0, 2, RN(ii)); - gtk_tooltips_set_tip( tips, natcheck, - _("Attempt to bypass NAT or firewall to allow incoming peer connections"), "" ); - ii++; - - /* create the model used by the two popup menus */ - model = GTK_TREE_MODEL(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT)); - gtk_list_store_append(GTK_LIST_STORE(model), &iter); - gtk_list_store_set(GTK_LIST_STORE(model), &iter, 1, 0, 0, - _("Use the torrent file where it is"), -1); - gtk_list_store_append(GTK_LIST_STORE(model), &iter); - gtk_list_store_set(GTK_LIST_STORE(model), &iter, 1, TR_TORNEW_SAVE_COPY, 0, - _("Keep a copy of the torrent file"), -1); - gtk_list_store_append(GTK_LIST_STORE(model), &iter); - gtk_list_store_set(GTK_LIST_STORE(model), &iter, 1, TR_TORNEW_SAVE_MOVE, 0, - _("Keep a copy and remove the original"), -1); - - /* std */ - label = gtk_label_new_with_mnemonic(_("For torrents added _normally:")); - gtk_label_set_mnemonic_widget(GTK_LABEL(label), addstd); - gtk_misc_set_alignment(GTK_MISC(label), 0, .5); - gtk_combo_box_set_model(GTK_COMBO_BOX(addstd), model); - rend = gtk_cell_renderer_text_new(); - gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(addstd), rend, TRUE); - gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(addstd), rend, "text", 0); - setupprefwidget(addstd, PREF_ADDSTD); - event = tipbox( label, tips, - _("Torrent files added via the toolbar, popup menu, and drag-and-drop") ); - gtk_table_attach_defaults(GTK_TABLE(table), event, 0, 1, RN(ii)); - event = tipbox( addstd, tips, - _("Torrent files added via the toolbar, popup menu, and drag-and-drop") ); - gtk_table_attach_defaults(GTK_TABLE(table), event, 1, 2, RN(ii)); - ii++; - - /* ipc */ - label = gtk_label_new_with_mnemonic( - _("For torrents added e_xternally\n(via the command-line):")); - gtk_label_set_mnemonic_widget(GTK_LABEL(label), addipc); - gtk_misc_set_alignment(GTK_MISC(label), 0, .5); - gtk_combo_box_set_model(GTK_COMBO_BOX(addipc), model); - rend = gtk_cell_renderer_text_new(); - gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(addipc), rend, TRUE); - gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(addipc), rend, "text", 0); - setupprefwidget(addipc, PREF_ADDIPC); - event = tipbox( label, tips, - _("For torrents added via the command-line only") ); - gtk_table_attach_defaults(GTK_TABLE(table), event, 0, 1, RN(ii)); - event = tipbox( addipc, tips, - _("For torrents added via the command-line only") ); - gtk_table_attach_defaults(GTK_TABLE(table), event, 1, 2, RN(ii)); - ii++; - -#undef RN - g_assert(rowcount == ii); - - gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(wind)->vbox), table); - g_signal_connect_data(wind, "response", G_CALLBACK(clickdialog), - data, freedata, 0); - gtk_widget_show_all(wind); - - return wind; -} - -static void -clicklimitbox( GtkWidget * widget, gpointer gdata ) -{ - GtkWidget ** widgets; - gboolean with, active; - int ii; - - widgets = gdata; - with = ( gboolean )GPOINTER_TO_INT( widgets[2] ); - - for(ii = 0; 2 > ii; ii++) - { - active = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( widget ) ); - gtk_widget_set_sensitive( widgets[ii], ( with ? active : !active ) ); - } -} - -static void -freedata(gpointer gdata, GClosure *closure SHUTUP) { - struct prefdata *data = gdata; - - g_list_free(data->prefwidgets); - g_object_unref(G_OBJECT(data->back)); - g_object_unref( data->tips ); - g_free(data); -} - -static void -clickdialog(GtkWidget *widget, int resp, gpointer gdata) { - struct prefdata *data = gdata; - char *errstr; - GList *ii; - - if(GTK_RESPONSE_APPLY == resp || GTK_RESPONSE_OK == resp) { - /* save all the prefs */ - for(ii = g_list_first(data->prefwidgets); NULL != ii; ii = ii->next) - saveprefwidget(data->parent, ii->data); - - /* write prefs to disk */ - cf_saveprefs(&errstr); - if(NULL != errstr) { - errmsg(data->parent, "%s", errstr); - g_free(errstr); - } - - applyprefs(data->back); - /* XXX would be nice to have errno strings, are they printed to stdout? */ - } - - if(GTK_RESPONSE_APPLY != resp) - gtk_widget_destroy(widget); -} - -void -applyprefs(TrBackend *back) { - struct { void (*func)(tr_handle_t*, int); - const char *use; const char *num; gboolean defuse; long def; } lim[] = { - {tr_setGlobalDownloadLimit, PREF_USEDOWNLIMIT, PREF_DOWNLIMIT, - DEF_USEDOWNLIMIT, DEF_DOWNLIMIT}, - {tr_setGlobalUploadLimit, PREF_USEUPLIMIT, PREF_UPLIMIT, - DEF_USEUPLIMIT, DEF_UPLIMIT}, - }; - const char *pref; - int ii; - tr_handle_t *tr = tr_backend_handle(back); - gboolean boolval; - - /* set upload and download limits */ - for(ii = 0; ii < (int)ALEN(lim); ii++) { - pref = cf_getpref(lim[ii].use); - if(!(NULL == pref ? lim[ii].defuse : strbool(pref))) - lim[ii].func(tr, -1); - else { - pref = cf_getpref(lim[ii].num); - lim[ii].func(tr, (NULL == pref ? lim[ii].def : strtol(pref, NULL, 10))); - } - } - - /* set the listening port */ - if(NULL != (pref = cf_getpref(PREF_PORT)) && - 0 < (ii = strtol(pref, NULL, 10)) && 0xffff >= ii) - tr_setBindPort(tr, ii); - - /* enable/disable NAT traversal */ - boolval = (NULL == (pref = cf_getpref(PREF_NAT)) ? DEF_NAT : strbool(pref)); - tr_natTraversalEnable(tr, boolval); -} +quitresp( GtkWidget * widget, gint resp, gpointer data ); void makeaddwind(GtkWindow *parent, add_torrents_func_t addfunc, void *cbdata) { @@ -494,7 +89,6 @@ makeaddwind(GtkWindow *parent, add_torrents_func_t addfunc, void *cbdata) { 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->addfunc = addfunc; data->data = cbdata; @@ -511,8 +105,8 @@ makeaddwind(GtkWindow *parent, add_torrents_func_t addfunc, void *cbdata) { gtk_box_pack_start_defaults(GTK_BOX(vbox), bbox); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(autocheck), TRUE); - if(NULL != (pref = cf_getpref(PREF_DIR))) - gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(getdir), pref); + gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( getdir ), + getdownloaddir() ); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dircheck), FALSE); gtk_widget_set_sensitive(getdir, FALSE); @@ -566,7 +160,7 @@ addresp(GtkWidget *widget, gint resp, gpointer gdata) { for(ii = files; NULL != ii; ii = ii->next) stupidgtk = g_list_append(stupidgtk, ii->data); flags = ( data->autostart ? TR_TORNEW_RUNNING : TR_TORNEW_PAUSED ); - flags |= addactionflag( cf_getpref( PREF_ADDSTD ) ); + flags |= addactionflag( tr_prefs_get( PREF_ID_ADDSTD ) ); data->addfunc( data->data, NULL, stupidgtk, dir, flags ); if(NULL != dir) g_free(dir); @@ -693,7 +287,7 @@ makeinfowind(GtkWindow *parent, TrTorrent *tor) { void promptfordir( GtkWindow * parent, add_torrents_func_t addfunc, void *cbdata, - GList * files, guint flags, const char * defaultdir ) + GList * files, guint flags ) { struct dirdata * stuff; GtkWidget * wind; @@ -711,7 +305,8 @@ promptfordir( GtkWindow * parent, add_torrents_func_t addfunc, void *cbdata, 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 ), defaultdir ); + gtk_file_chooser_set_filename( GTK_FILE_CHOOSER( wind ), + getdownloaddir() ); g_signal_connect( G_OBJECT( wind ), "response", G_CALLBACK( promptresp ), stuff ); @@ -740,3 +335,40 @@ promptresp( GtkWidget * widget, gint resp, gpointer data ) g_free( stuff ); gtk_widget_destroy( widget ); } + +void +askquit( GtkWindow * parent, callbackfunc_t func, void * cbdata ) +{ + struct quitdata * stuff; + GtkWidget * wind; + + 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 ); +} diff --git a/gtk/dialogs.h b/gtk/dialogs.h index 009d872fe..92d7556c1 100644 --- a/gtk/dialogs.h +++ b/gtk/dialogs.h @@ -47,6 +47,10 @@ makeinfowind(GtkWindow *parent, TrTorrent *tor); /* prompt for a download directory for torrents, then add them */ void promptfordir( GtkWindow * parent, add_torrents_func_t addfunc, void *cbdata, - GList * files, guint flags, const char * defaultdir ); + GList * files, guint flags ); + +/* prompt if the user wants to quit, calls func with cbdata if they do */ +void +askquit( GtkWindow * parent, callbackfunc_t func, void * cbdata ); #endif /* TG_PREFS_H */ diff --git a/gtk/ipc.c b/gtk/ipc.c index 91199ddfc..4dc74928d 100644 --- a/gtk/ipc.c +++ b/gtk/ipc.c @@ -1,7 +1,7 @@ /****************************************************************************** * $Id$ * - * Copyright (c) 2006 Transmission authors and contributors + * Copyright (c) 2006-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"), @@ -40,6 +40,7 @@ #include "conf.h" #include "io.h" #include "ipc.h" +#include "tr_prefs.h" #include "util.h" #define PROTOVERS 1 /* IPC protocol version */ @@ -48,20 +49,26 @@ #define MSG_VERSION ("version") /* list of strings, full paths to torrent files to load */ #define MSG_ADDFILES ("addfiles") +/* request that the server quit */ +#define MSG_QUIT ("quit") -enum contype { CON_SERV, CON_ADDFILE }; +enum contype { CON_SERV, CON_CLIENT }; struct constate_serv { void *wind; add_torrents_func_t addfunc; + callbackfunc_t quitfunc; void *cbdata; }; -struct constate_addfile { +enum client_cmd { CCMD_ADD, CCMD_QUIT }; + +struct constate_client { GMainLoop *loop; + enum client_cmd cmd; GList *files; gboolean *succeeded; - unsigned int addid; + unsigned int msgid; }; struct constate; @@ -75,14 +82,10 @@ struct constate { enum contype type; union { struct constate_serv serv; - struct constate_addfile addfile; + struct constate_client client; } u; }; -void -ipc_socket_setup(void *parent, add_torrents_func_t addfunc, void *cbdata); -gboolean -ipc_sendfiles_blocking(GList *files); static void serv_bind(struct constate *con); static void @@ -105,16 +108,19 @@ all_io_closed(GSource *source, void *vdata); static void srv_addfile(struct constate *con, const char *name, benc_val_t *val); static void +srv_quit( struct constate * con, const char * name, benc_val_t * val ); +static void afc_version(struct constate *con, const char *name, benc_val_t *val); static void afc_io_sent(GSource *source, unsigned int id, void *vdata); static const struct handlerdef gl_funcs_serv[] = { {MSG_ADDFILES, srv_addfile}, + {MSG_QUIT, srv_quit}, {NULL, NULL} }; -static const struct handlerdef gl_funcs_addfile[] = { +static const struct handlerdef gl_funcs_client[] = { {MSG_VERSION, afc_version}, {NULL, NULL} }; @@ -123,7 +129,9 @@ static const struct handlerdef gl_funcs_addfile[] = { static char *gl_sockpath = NULL; void -ipc_socket_setup(void *parent, add_torrents_func_t addfunc, void *cbdata) { +ipc_socket_setup( void * parent, add_torrents_func_t addfunc, + callbackfunc_t quitfunc, void * cbdata ) +{ struct constate *con; con = g_new0(struct constate, 1); @@ -133,13 +141,16 @@ ipc_socket_setup(void *parent, add_torrents_func_t addfunc, void *cbdata) { con->type = CON_SERV; con->u.serv.wind = parent; con->u.serv.addfunc = addfunc; + con->u.serv.quitfunc = quitfunc; con->u.serv.cbdata = cbdata; serv_bind(con); } -gboolean -ipc_sendfiles_blocking(GList *files) { +static gboolean +blocking_client( enum client_cmd cmd, GList * files ) +{ + struct constate *con; char *path; gboolean ret = FALSE; @@ -147,12 +158,13 @@ ipc_sendfiles_blocking(GList *files) { con = g_new0(struct constate, 1); con->source = NULL; con->fd = -1; - con->funcs = gl_funcs_addfile; - con->type = CON_ADDFILE; - con->u.addfile.loop = g_main_loop_new(NULL, TRUE); - con->u.addfile.files = files; - con->u.addfile.succeeded = &ret; - con->u.addfile.addid = 0; + con->funcs = gl_funcs_client; + con->type = CON_CLIENT; + con->u.client.loop = g_main_loop_new(NULL, TRUE); + con->u.client.cmd = cmd; + con->u.client.files = files; + con->u.client.succeeded = &ret; + con->u.client.msgid = 0; path = cf_sockname(); if(!client_connect(path, con)) { @@ -161,11 +173,23 @@ ipc_sendfiles_blocking(GList *files) { return FALSE; } - g_main_loop_run(con->u.addfile.loop); + g_main_loop_run(con->u.client.loop); return ret; } +gboolean +ipc_sendfiles_blocking( GList * files ) +{ + return blocking_client( CCMD_ADD, files ); +} + +gboolean +ipc_sendquit_blocking( void ) +{ + return blocking_client( CCMD_QUIT, NULL ); +} + /* open a local socket for clients connections */ static void serv_bind(struct constate *con) { @@ -356,9 +380,9 @@ destroycon(struct constate *con) { switch(con->type) { case CON_SERV: break; - case CON_ADDFILE: - freestrlist(con->u.addfile.files); - g_main_loop_quit(con->u.addfile.loop); + case CON_CLIENT: + freestrlist(con->u.client.files); + g_main_loop_quit(con->u.client.loop); break; } } @@ -375,6 +399,7 @@ srv_addfile(struct constate *con, const char *name SHUTUP, benc_val_t *val) { struct constate_serv *srv = &con->u.serv; GList *files; int ii; + guint flags; if(TYPE_LIST == val->type) { files = NULL; @@ -383,47 +408,68 @@ srv_addfile(struct constate *con, const char *name SHUTUP, benc_val_t *val) { /* XXX somehow escape invalid utf-8 */ g_utf8_validate(val->val.l.vals[ii].val.s.s, -1, NULL)) files = g_list_append(files, val->val.l.vals[ii].val.s.s); - srv->addfunc(srv->cbdata, NULL, files, NULL, - addactionflag(cf_getpref(PREF_ADDIPC))); + flags = addactionflag( tr_prefs_get( PREF_ID_ADDIPC ) ); + srv->addfunc( srv->cbdata, NULL, files, NULL, flags ); g_list_free(files); } } +static void +srv_quit( struct constate * con, const char * name SHUTUP, + benc_val_t * val SHUTUP ) +{ + struct constate_serv * srv; + + srv = &con->u.serv; + srv->quitfunc( srv->cbdata ); +} + static void afc_version(struct constate *con, const char *name SHUTUP, benc_val_t *val) { - struct constate_addfile *afc = &con->u.addfile; + struct constate_client *afc = &con->u.client; GList *file; benc_val_t list, *str; if(TYPE_INT != val->type || PROTOVERS != val->val.i) { fprintf(stderr, _("bad IPC protocol version\n")); destroycon(con); - } else { - /* XXX handle getting a non-version tag, invalid data, - or nothing (read timeout) */ - bzero(&list, sizeof(list)); - list.type = TYPE_LIST; - list.val.l.alloc = g_list_length(afc->files); - list.val.l.vals = g_new0(benc_val_t, list.val.l.alloc); - for(file = afc->files; NULL != file; file = file->next) { - str = list.val.l.vals + list.val.l.count; - str->type = TYPE_STR; - str->val.s.i = strlen(file->data); - str->val.s.s = file->data; - list.val.l.count++; - } - g_list_free(afc->files); - afc->files = NULL; - afc->addid = send_msg(con, MSG_ADDFILES, &list); - tr_bencFree(&list); + return; + } + + /* XXX handle getting a non-version tag, invalid data, + or nothing (read timeout) */ + switch( afc->cmd ) + { + case CCMD_ADD: + list.type = TYPE_LIST; + list.val.l.alloc = g_list_length(afc->files); + list.val.l.count = 0; + list.val.l.vals = g_new0(benc_val_t, list.val.l.alloc); + for(file = afc->files; NULL != file; file = file->next) { + str = list.val.l.vals + list.val.l.count; + str->type = TYPE_STR; + str->val.s.i = strlen(file->data); + str->val.s.s = file->data; + list.val.l.count++; + } + g_list_free(afc->files); + afc->files = NULL; + afc->msgid = send_msg(con, MSG_ADDFILES, &list); + tr_bencFree(&list); + break; + case CCMD_QUIT: + bzero( &list, sizeof( list ) ); + list.type = TYPE_STR; + afc->msgid = send_msg( con, MSG_QUIT, &list ); + break; } } static void afc_io_sent(GSource *source SHUTUP, unsigned int id, void *vdata) { - struct constate_addfile *afc = &((struct constate*)vdata)->u.addfile; + struct constate_client *afc = &((struct constate*)vdata)->u.client; - if(0 < id && afc->addid == id) { + if(0 < id && afc->msgid == id) { *(afc->succeeded) = TRUE; destroycon(vdata); } diff --git a/gtk/ipc.h b/gtk/ipc.h index 407ebed5f..0960d5dd8 100644 --- a/gtk/ipc.h +++ b/gtk/ipc.h @@ -1,7 +1,7 @@ /****************************************************************************** * $Id$ * - * Copyright (c) 2006 Transmission authors and contributors + * Copyright (c) 2006-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"), @@ -28,9 +28,13 @@ #include "util.h" void -ipc_socket_setup(void *wind, add_torrents_func_t addfunc, void *cbdata); +ipc_socket_setup( void * wind, add_torrents_func_t addfunc, + callbackfunc_t quitfunc, void * cbdata ); gboolean -ipc_sendfiles_blocking(GList *files); +ipc_sendfiles_blocking( GList * files ); + +gboolean +ipc_sendquit_blocking( void ); #endif /* TG_IPC_H */ diff --git a/gtk/main.c b/gtk/main.c index b943ffee9..c42184cd4 100644 --- a/gtk/main.c +++ b/gtk/main.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -40,12 +41,15 @@ #include "ipc.h" #include "msgwin.h" #include "tr_backend.h" -#include "tr_torrent.h" #include "tr_cell_renderer_progress.h" +#include "tr_icon.h" +#include "tr_prefs.h" +#include "tr_torrent.h" #include "tr_window.h" -#include "transmission.h" #include "util.h" +#include "transmission.h" + #include "img_icon_full.h" /* time in seconds to wait for torrents to stop when exiting */ @@ -57,20 +61,24 @@ /* interval in milliseconds to check for stopped torrents and update display */ #define EXIT_CHECK_INTERVAL 500 +/* number of fatal signals required to cause an immediate exit */ +#define SIGCOUNT_MAX 3 + struct cbdata { - TrBackend *back; - GtkWindow *wind; - GtkTreeModel *model; - guint timer; - gboolean prefsopen; - gboolean msgwinopen; - gboolean closing; + TrBackend * back; + GtkWindow * wind; + GtkTreeModel * model; + TrIcon * icon; + TrPrefs * prefs; + guint timer; + gboolean msgwinopen; + gboolean closing; }; struct exitdata { - struct cbdata *cbdata; - time_t started; - guint timer; + struct cbdata * cbdata; + time_t started; + guint timer; }; enum action @@ -82,6 +90,7 @@ enum action ACT_INFO, ACT_PREF, ACT_DEBUG, + ACT_ICON, ACTION_COUNT, }; @@ -108,23 +117,32 @@ actions[] = N_("Customize application behavior") }, { N_("Open debug window"), NULL, ACTF_MENU | ACTF_ALWAYS, NULL }, + /* this isn't a terminator for the list, it's ACT_ICON */ + { NULL, NULL, 0, NULL }, }; #define CBDATA_PTR "callback-data-pointer" -#define SIGCOUNT_MAX 3 - static sig_atomic_t global_sigcount = 0; static GList * -readargs(int argc, char **argv); - -static void -makewind(TrWindow *wind, TrBackend *back, benc_val_t *state, GList *args); -static void -quittransmission(struct cbdata *data); +readargs( int argc, char ** argv, gboolean * sendquit, gboolean * paused ); static gboolean -winclose(GtkWidget *widget, GdkEvent *event, gpointer gdata); +sendremote( GList * files, gboolean sendquit ); +static void +gtksetup( int * argc, char *** argv ); +static void +appsetup( TrWindow * wind, benc_val_t * state, GList * args, gboolean paused ); +static void +winsetup( struct cbdata * cbdata, TrWindow * wind ); +static void +remakewind( struct cbdata * cbdata ); +static void +makeicon( struct cbdata * cbdata ); +static gboolean +winclose( GtkWidget * widget, GdkEvent * event, gpointer gdata ); +static void +wannaquit( void * vdata ); static gboolean exitcheck(gpointer gdata); static void @@ -133,12 +151,18 @@ static void gotdrag(GtkWidget *widget, GdkDragContext *dc, gint x, gint y, GtkSelectionData *sel, guint info, guint time, gpointer gdata); +static void +readinitialprefs( struct cbdata * cbdata ); +static void +prefschanged( GtkWidget * widget, int id, gpointer data ); static gboolean updatemodel(gpointer gdata); static void -boolwindclosed(GtkWidget *widget SHUTUP, gpointer gdata); +boolwindclosed(GtkWidget *widget, gpointer gdata); static void windact(GtkWidget *widget, int action, gpointer gdata); +static GList * +getselection( struct cbdata * cbdata ); static void handleaction(struct cbdata *data, enum action action); @@ -155,220 +179,330 @@ static void fatalsig(int sig); int -main(int argc, char **argv) { - GtkWidget *mainwind, *preferr, *stateerr; - char *err; - TrBackend *back; - benc_val_t *state; - GList *argfiles; - gboolean didinit, didlock; - GdkPixbuf * icon; +main( int argc, char ** argv ) +{ + GtkWindow * mainwind; + char * err; + benc_val_t * state; + GList * argfiles; + gboolean didinit, didlock, sendquit, startpaused; - safepipe(); - - argfiles = readargs(argc, argv); - - didinit = cf_init(tr_getPrefsDirectory(), NULL); - didlock = FALSE; - if(NULL != argfiles && didinit && !(didlock = cf_lock(NULL))) - return !ipc_sendfiles_blocking(argfiles); - - setupsighandlers(); - - gtk_init(&argc, &argv); - - bindtextdomain("transmission-gtk", LOCALEDIR); - bind_textdomain_codeset("transmission-gtk", "UTF-8"); - textdomain("transmission-gtk"); - - g_set_application_name(_("Transmission")); -#if 0 - /* this isn't used in transmission-gtk itself, it's for the .desktop file */ - N_("BitTorrent Client"); - /* this too */ - N_("A free, lightweight client with a simple, intuitive interface"); -#endif - - gtk_rc_parse_string( - "style \"transmission-standard\" {\n" - " GtkDialog::action-area-border = 6\n" - " GtkDialog::button-spacing = 12\n" - " GtkDialog::content-area-border = 6\n" - "}\n" - "widget \"TransmissionDialog\" style \"transmission-standard\"\n"); - - icon = gdk_pixbuf_new_from_inline( -1, tr_icon_full, FALSE, NULL ); - gtk_window_set_default_icon( icon ); - g_object_unref( icon ); - - if(didinit || cf_init(tr_getPrefsDirectory(), &err)) { - if(didlock || cf_lock(&err)) { - - /* create main window now so any error dialogs can be it's children */ - mainwind = tr_window_new(); - preferr = NULL; - stateerr = NULL; - - cf_loadprefs(&err); - if(NULL != err) { - preferr = errmsg(GTK_WINDOW(mainwind), "%s", err); - g_free(err); - } - state = cf_loadstate(&err); - if(NULL != err) { - stateerr = errmsg(GTK_WINDOW(mainwind), "%s", err); - g_free(err); - } - - /* set libT message level */ - msgwin_loadpref(); - - back = tr_backend_new(); - - /* apply a few prefs */ - applyprefs(back); - - makewind( TR_WINDOW( mainwind ), back, state, argfiles ); - - if(NULL != state) - cf_freestate(state); - g_object_unref(back); - - if(NULL != preferr) - gtk_widget_show_all(preferr); - if(NULL != stateerr) - gtk_widget_show_all(stateerr); - } else { - gtk_widget_show(errmsg_full(NULL, (callbackfunc_t)gtk_main_quit, - NULL, "%s", err)); - g_free(err); + safepipe(); /* ignore SIGPIPE */ + argfiles = readargs( argc, argv, &sendquit, &startpaused ); + didinit = cf_init( tr_getPrefsDirectory(), NULL ); + didlock = FALSE; + if( didinit ) + { + /* maybe send remote commands, also try cf_lock() */ + didlock = sendremote( argfiles, sendquit ); + } + setupsighandlers(); /* set up handlers for fatal signals */ + gtksetup( &argc, &argv ); /* set up gtk and gettext */ + + if( ( didinit || cf_init( tr_getPrefsDirectory(), &err ) ) && + ( didlock || cf_lock( &err ) ) ) + { + /* create main window now to be a parent to any error dialogs */ + mainwind = GTK_WINDOW( tr_window_new() ); + + /* try to load prefs and saved state */ + cf_loadprefs( &err ); + if( NULL != err ) + { + errmsg( mainwind, "%s", err ); + g_free( err ); + } + state = cf_loadstate( &err ); + if( NULL != err ) + { + errmsg( mainwind, "%s", err ); + g_free( err ); + } + + msgwin_loadpref(); /* set message level here before tr_init() */ + appsetup( TR_WINDOW( mainwind ), state, argfiles, startpaused ); + cf_freestate( state ); + } + else + { + gtk_widget_show( errmsg_full( NULL, (callbackfunc_t)gtk_main_quit, + NULL, "%s", err ) ); + g_free( err ); } - } else { - gtk_widget_show(errmsg_full(NULL, (callbackfunc_t)gtk_main_quit, - NULL, "%s", err)); - g_free(err); - } - if(NULL != argfiles) freestrlist(argfiles); - gtk_main(); + gtk_main(); - return 0; + return 0; } GList * -readargs(int argc, char **argv) { - char *name; +readargs( int argc, char ** argv, gboolean * sendquit, gboolean * startpaused ) +{ + struct option opts[] = + { + { "help", no_argument, NULL, 'h' }, + { "paused", no_argument, NULL, 'p' }, + { "quit", no_argument, NULL, 'q' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } + }; + int opt; - if(NULL == (name = strrchr(argv[0], '/')) || '\0' == *(++name)) - name = argv[0]; + *sendquit = FALSE; + *startpaused = FALSE; - while(0 < --argc) { - argv++; - if(0 == strcmp("--", *argv)) - return checkfilenames(argc - 1, argv + 1); - else if('-' != argv[0][0]) - return checkfilenames(argc, argv); - else if(0 == strcmp("-v", *argv) || 0 == strcmp("--version", *argv)) { - printf("%s %s (%d) http://transmission.m0k.org/\n", - name, VERSION_STRING, VERSION_REVISION); - exit(0); + gtk_parse_args( &argc, &argv ); + + while( 0 <= ( opt = getopt_long( argc, argv, "hpqv", opts, NULL ) ) ) + { + switch( opt ) + { + case 'p': + *startpaused = TRUE; + break; + case 'q': + *sendquit = TRUE; + break; + case 'v': + case 'h': + printf( +_("usage: %1$s [-hpq] [files...]\n" + "\n" + "Transmission %2$s (r%3$d) http://transmission.m0k.org/\n" + "A free, lightweight BitTorrent client with a simple, intuitive interface\n" + "\n" + " -h --help display this message and exit\n" + " -p --paused start with all torrents paused\n" + " -q --quit request that the running %1$s instance quit\n" + "\n" + "Only one instance of %1$s may run at one time. Multiple\n" + "torrent files may be loaded at startup by adding them to the command\n" + "line. If %1$s is already running, those torrents will be\n" + "opened in the running instance.\n"), + g_get_prgname(), VERSION_STRING, VERSION_REVISION ); + exit(0); + break; + } } - else if(0 == strcmp("-h", *argv) || 0 == strcmp("--help", *argv)) { - printf("usage: %1$s [-hv] [files...]\n\n" -"If %1$s is already running then a second copy will not be\n" -"started, any torrents on the command-line will be opened in the first.\n", - name); - exit(0); - } - } - return NULL; + argc -= optind; + argv += optind; + + return checkfilenames( argc, argv ); +} + +static gboolean +sendremote( GList * files, gboolean sendquit ) +{ + gboolean didlock; + + didlock = cf_lock( NULL ); + + if( NULL != files ) + { + /* send files if there's another instance, otherwise start normally */ + if( !didlock ) + { + exit( ipc_sendfiles_blocking( files ) ? 0 : 1 ); + } + } + + if( sendquit ) + { + /* either send a quit message or exit if no other instance */ + if( !didlock ) + { + exit( ipc_sendquit_blocking() ? 0 : 1 ); + } + exit( 0 ); + } + + return didlock; } static void -makewind( TrWindow * wind, TrBackend * back, benc_val_t * state, GList * args) +gtksetup( int * argc, char *** argv ) { - GType types[] = { - /* info->name, info->totalSize, status, error, errorString, */ - G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, - /* progress, rateDownload, rateUpload, eta, peersTotal, */ - G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT, - /* peersUploading, peersDownloading, downloaded, uploaded */ - G_TYPE_INT, G_TYPE_INT, G_TYPE_UINT64, G_TYPE_UINT64, - /* the torrent object */ - TR_TORRENT_TYPE}; - struct cbdata *data = g_new0(struct cbdata, 1); - GtkListStore *store; - unsigned int ii; - GtkWidget *drag; + GdkPixbuf * icon; - g_assert(MC_ROW_COUNT == ALEN(types)); - store = gtk_list_store_newv(MC_ROW_COUNT, types); + gtk_init( argc, argv ); - g_object_ref(G_OBJECT(back)); - data->back = back; - data->wind = GTK_WINDOW(wind); - data->timer = 0; - data->model = GTK_TREE_MODEL(store); - data->prefsopen = FALSE; - data->msgwinopen = FALSE; - data->closing = FALSE; + bindtextdomain( "transmission-gtk", LOCALEDIR ); + bind_textdomain_codeset( "transmission-gtk", "UTF-8" ); + textdomain( "transmission-gtk" ); - g_assert( ACTION_COUNT == ALEN( actions ) ); - for( ii = 0; ii < ALEN( actions ); ii++ ) - { - tr_window_action_add( wind, ii, actions[ii].flags, - gettext( actions[ii].label ), - actions[ii].icon, - gettext( actions[ii].tooltip ) ); - } - g_object_set( wind, "model", data->model, - "double-click-action", ACT_INFO, NULL); + g_set_application_name( _("Transmission") ); - g_signal_connect( wind, "action", G_CALLBACK( windact ), data ); - g_signal_connect( wind, "delete_event", G_CALLBACK( winclose ), data ); + /* tweak some style properties in dialogs to get closer to the GNOME HiG */ + gtk_rc_parse_string( + "style \"transmission-standard\"\n" + "{\n" + " GtkDialog::action-area-border = 6\n" + " GtkDialog::button-spacing = 12\n" + " GtkDialog::content-area-border = 6\n" + "}\n" + "widget \"TransmissionDialog\" style \"transmission-standard\"\n" ); - g_object_get( wind, "drag-widget", &drag, NULL ); - setupdrag( drag, data ); - - addtorrents(data, state, args, NULL, addactionflag(cf_getpref(PREF_ADDIPC))); - - data->timer = g_timeout_add(UPDATE_INTERVAL, updatemodel, data); - updatemodel(data); - - /* this shows the window */ - tr_window_size_hack( wind ); - - /* set up the ipc socket now that we're ready to get torrents from it */ - ipc_socket_setup(GTK_WINDOW(wind), addtorrents, data); + icon = gdk_pixbuf_new_from_inline( -1, tr_icon_full, FALSE, NULL ); + gtk_window_set_default_icon( icon ); + g_object_unref( icon ); } static void -quittransmission( struct cbdata * data ) +appsetup( TrWindow * wind, benc_val_t * state, GList * args, gboolean paused ) { - g_object_unref( G_OBJECT( data->back ) ); - if( NULL != data->wind ) + GType types[] = { - gtk_widget_destroy( GTK_WIDGET( data->wind ) ); - } - g_object_unref( data->model ); - if( 0 < data->timer ) + /* info->name, info->totalSize, status, error, errorString, */ + G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, + /* progress, rateDownload, rateUpload, eta, peersTotal, */ + G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT, + /* peersUploading, peersDownloading, downloaded, uploaded */ + G_TYPE_INT, G_TYPE_INT, G_TYPE_UINT64, G_TYPE_UINT64, + /* the TrTorrent object */ + TR_TORRENT_TYPE, + }; + struct cbdata * cbdata; + GtkListStore * store; + guint flags; + + /* create the model used to store torrent data */ + g_assert( ALEN( types ) == MC_ROW_COUNT ); + store = gtk_list_store_newv( MC_ROW_COUNT, types ); + + /* fill out cbdata */ + cbdata = g_new0( struct cbdata, 1 ); + cbdata->back = tr_backend_new(); + cbdata->wind = NULL; + cbdata->model = GTK_TREE_MODEL(store); + cbdata->icon = NULL; + cbdata->prefs = NULL; + cbdata->timer = 0; + cbdata->msgwinopen = FALSE; + cbdata->closing = FALSE; + + /* apply a few prefs */ + readinitialprefs( cbdata ); + + /* set up main window */ + winsetup( cbdata, wind ); + + /* add torrents from command-line and saved state */ + flags = addactionflag( tr_prefs_get( PREF_ID_ADDIPC ) ); + g_assert( !( flags & ( TR_TORNEW_PAUSED | TR_TORNEW_RUNNING ) ) ); + if( paused ) { - g_source_remove( data->timer ); + flags |= TR_TORNEW_PAUSED; } - g_free( data ); - gtk_main_quit(); + addtorrents( cbdata, state, args, NULL, flags ); + + /* start model update timer */ + cbdata->timer = g_timeout_add( UPDATE_INTERVAL, updatemodel, cbdata ); + updatemodel( cbdata ); + + /* this shows the window */ + tr_window_size_hack( wind ); + + /* set up the ipc socket now that we're ready to get torrents from it */ + ipc_socket_setup( GTK_WINDOW( wind ), addtorrents, wannaquit, cbdata ); } -gboolean -winclose(GtkWidget *widget SHUTUP, GdkEvent *event SHUTUP, gpointer gdata) { - struct cbdata *data = gdata; +static void +winsetup( struct cbdata * cbdata, TrWindow * wind ) +{ + int ii; + GtkWidget * drag; + + g_assert( ACTION_COUNT == ALEN( actions ) ); + g_assert( NULL == cbdata->wind ); + cbdata->wind = GTK_WINDOW( wind ); + for( ii = 0; ii < ALEN( actions ); ii++ ) + { + tr_window_action_add( wind, ii, actions[ii].flags, + gettext( actions[ii].label ), actions[ii].icon, + gettext( actions[ii].tooltip ) ); + } + g_object_set( wind, "model", cbdata->model, + "double-click-action", ACT_INFO, NULL); + + g_signal_connect( wind, "action", G_CALLBACK( windact ), cbdata ); + g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata ); + + g_object_get( wind, "drag-widget", &drag, NULL ); + setupdrag( drag, cbdata ); +} + +static void +remakewind( struct cbdata * cbdata ) +{ + GtkWidget * win; + + if( NULL != cbdata->wind ) + { + return; + } + + /* create window */ + win = tr_window_new(); + winsetup( cbdata, TR_WINDOW( win ) ); + + /* this shows the window */ + tr_window_size_hack( TR_WINDOW( win ) ); +} + +static void +makeicon( struct cbdata * cbdata ) +{ + TrIcon * icon; + + if( NULL != cbdata->icon ) + { + return; + } + + icon = tr_icon_new(); + g_object_set( icon, "activate-action", ACT_ICON, NULL); + g_signal_connect( icon, "action", G_CALLBACK( windact ), cbdata ); + + cbdata->icon = icon; +} + +static gboolean +winclose( GtkWidget * widget SHUTUP, GdkEvent * event SHUTUP, gpointer gdata ) +{ + struct cbdata * cbdata; + + cbdata = gdata; + + if( NULL != cbdata->icon && tr_icon_docked( cbdata->icon ) ) + { + gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) ); + cbdata->wind = NULL; + } + else + { + askquit( cbdata->wind, wannaquit, cbdata ); + } + + /* don't propagate event further */ + return TRUE; +} + +static void +wannaquit( void * vdata ) +{ + struct cbdata * data; struct exitdata *edata; GtkTreeIter iter; TrTorrent *tor; + data = vdata; + if( data->closing ) + { + return; + } data->closing = TRUE; /* stop the update timer */ @@ -402,36 +536,68 @@ winclose(GtkWidget *widget SHUTUP, GdkEvent *event SHUTUP, gpointer gdata) { if(exitcheck(edata)) { /* yes, start the exit timer and disable widgets */ edata->timer = g_timeout_add(EXIT_CHECK_INTERVAL, exitcheck, edata); - gtk_widget_set_sensitive( GTK_WIDGET( data->wind ), FALSE ); + if( NULL != data->wind ) + { + gtk_widget_set_sensitive( GTK_WIDGET( data->wind ), FALSE ); + } } - - /* returning FALSE means to destroy the window */ - return TRUE; } -gboolean -exitcheck(gpointer gdata) { - struct exitdata *data = gdata; - tr_handle_status_t * hstat; +static gboolean +exitcheck( gpointer gdata ) +{ + struct exitdata * edata; + struct cbdata * cbdata; + tr_handle_status_t * hstat; - hstat = tr_handleStatus( tr_backend_handle( data->cbdata->back ) ); + edata = gdata; + cbdata = edata->cbdata; + hstat = tr_handleStatus( tr_backend_handle( cbdata->back ) ); - /* keep going if we haven't hit the exit timeout and - we either have torrents left or nat traversal is stopping */ - if( time( NULL ) - data->started < TRACKER_EXIT_TIMEOUT && - ( !tr_backend_torrents_stopped( data->cbdata->back ) || - TR_NAT_TRAVERSAL_DISABLED != hstat->natTraversalStatus ) ) { - updatemodel(data->cbdata); - return TRUE; - } + /* keep going if we haven't hit the exit timeout and + we either have torrents left or nat traversal is active */ + if( time( NULL ) - edata->started < TRACKER_EXIT_TIMEOUT ) + { + if( !tr_backend_torrents_stopped( cbdata->back, FALSE ) || + TR_NAT_TRAVERSAL_DISABLED != hstat->natTraversalStatus ) + { + updatemodel( cbdata ); + return TRUE; + } + } + else + { + /* time the remaining torrents out so they signal politely-stopped */ + tr_backend_torrents_stopped( cbdata->back, TRUE ); + } - /* exit otherwise */ - if(0 < data->timer) - g_source_remove(data->timer); - quittransmission(data->cbdata); - g_free(data); + /* exit otherwise */ + if( 0 < edata->timer ) + { + g_source_remove( edata->timer ); + } + g_free( edata ); + /* The prefs window need to be destroyed first as destroying it may + trigger callbacks that use cbdata->back. Ick. */ + if( NULL != cbdata->prefs ) + { + gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) ); + } + g_object_unref( G_OBJECT( cbdata->back ) ); + if( NULL != cbdata->wind ) + { + gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) ); + } + g_object_unref( cbdata->model ); + if( NULL != cbdata->icon ) + { + g_object_unref( cbdata->icon ); + } + g_assert( 0 == cbdata->timer ); + g_free( cbdata ); + gtk_main_quit(); - return FALSE; + return FALSE; } static void @@ -506,9 +672,11 @@ gotdrag(GtkWidget *widget SHUTUP, GdkDragContext *dc, gint x SHUTUP, } /* try to add any torrents we found */ - if(NULL != paths) - addtorrents(data, NULL, paths, NULL, - addactionflag(cf_getpref(PREF_ADDSTD))); + if( NULL != paths ) + { + addtorrents( data, NULL, paths, NULL, + addactionflag( tr_prefs_get( PREF_ID_ADDSTD ) ) ); + } freestrlist(freeables); g_free(files); } @@ -530,6 +698,87 @@ setupdrag(GtkWidget *widget, struct cbdata *data) { ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE); } +static void +readinitialprefs( struct cbdata * cbdata ) +{ + int prefs[] = + { + PREF_ID_PORT, + PREF_ID_USEDOWNLIMIT, + PREF_ID_USEUPLIMIT, + PREF_ID_NAT, + PREF_ID_ICON, + }; + int ii; + + for( ii = 0; ALEN( prefs ) > ii; ii++ ) + { + prefschanged( NULL, prefs[ii], cbdata ); + } +} + +static void +prefschanged( GtkWidget * widget SHUTUP, int id, gpointer data ) +{ + struct cbdata * cbdata; + tr_handle_t * tr; + int num; + + cbdata = data; + tr = tr_backend_handle( cbdata->back ); + + switch( id ) + { + case PREF_ID_PORT: + tr_setBindPort( tr, tr_prefs_get_int_with_default( id ) ); + break; + + case PREF_ID_USEDOWNLIMIT: + case PREF_ID_DOWNLIMIT: + num = -1; + if( tr_prefs_get_bool_with_default( PREF_ID_USEDOWNLIMIT ) ) + { + num = tr_prefs_get_int_with_default( PREF_ID_DOWNLIMIT ); + } + tr_setGlobalDownloadLimit( tr, num ); + break; + + case PREF_ID_USEUPLIMIT: + case PREF_ID_UPLIMIT: + num = -1; + if( tr_prefs_get_bool_with_default( PREF_ID_USEUPLIMIT ) ) + { + num = tr_prefs_get_int_with_default( PREF_ID_UPLIMIT ); + } + tr_setGlobalUploadLimit( tr, num ); + break; + + case PREF_ID_NAT: + tr_natTraversalEnable( tr, tr_prefs_get_bool_with_default( id ) ); + break; + + case PREF_ID_ICON: + if( tr_prefs_get_bool_with_default( id ) ) + { + makeicon( cbdata ); + } + else if( NULL != cbdata->icon ) + { + g_object_unref( cbdata->icon ); + cbdata->icon = NULL; + } + break; + + case PREF_ID_DIR: + case PREF_ID_ASKDIR: + case PREF_ID_ADDSTD: + case PREF_ID_ADDIPC: + case PREF_ID_MSGLEVEL: + case PREF_MAX_ID: + break; + } +} + gboolean updatemodel(gpointer gdata) { struct cbdata *data = gdata; @@ -539,9 +788,10 @@ updatemodel(gpointer gdata) { GtkTreeIter iter; float up, down; - if(0 < global_sigcount) { - quittransmission(data); - return FALSE; + if( !data->closing && 0 < global_sigcount ) + { + wannaquit( data ); + return FALSE; } if(gtk_tree_model_get_iter_first(data->model, &iter)) { @@ -562,12 +812,17 @@ updatemodel(gpointer gdata) { } /* update the main window's statusbar and toolbar buttons */ - tr_torrentRates( tr_backend_handle( data->back ), &down, &up ); - tr_window_update( TR_WINDOW(data->wind), down, up ); + if( NULL != data->wind ) + { + tr_torrentRates( tr_backend_handle( data->back ), &down, &up ); + tr_window_update( TR_WINDOW( data->wind ), down, up ); + } /* check for politely stopped torrents unless we're exiting */ - if(!data->closing) - tr_backend_torrents_stopped(data->back); + if( !data->closing ) + { + tr_backend_torrents_stopped( data->back, FALSE ); + } /* update the message window */ msgwin_update(); @@ -589,12 +844,34 @@ windact( GtkWidget * wind SHUTUP, int action, gpointer gdata ) handleaction( gdata, action ); } +/* returns a GList containing a GtkTreeRowReference to each selected row */ +static GList * +getselection( struct cbdata * cbdata ) +{ + GtkTreeSelection * sel; + GList * rows, * ii; + GtkTreeRowReference * ref; + + if( NULL == cbdata->wind ) + { + return NULL; + } + g_object_get( cbdata->wind, "selection", &sel, NULL ); + rows = gtk_tree_selection_get_selected_rows( sel, NULL ); + for( ii = rows; NULL != ii; ii = ii->next ) + { + ref = gtk_tree_row_reference_new( cbdata->model, ii->data ); + gtk_tree_path_free( ii->data ); + ii->data = ref; + } + + return rows; +} + static void handleaction( struct cbdata * data, enum action act ) { - GtkTreeSelection *sel; GList *rows, *ii; - GtkTreeRowReference *ref; GtkTreePath *path; GtkTreeIter iter; TrTorrent *tor; @@ -610,13 +887,16 @@ handleaction( struct cbdata * data, enum action act ) makeaddwind( data->wind, addtorrents, data ); return; case ACT_PREF: - if( !data->prefsopen ) + if( NULL != data->prefs ) { - data->prefsopen = TRUE; - win = makeprefwindow( data->wind, data->back ); - g_signal_connect( win, "destroy", G_CALLBACK( boolwindclosed ), - &data->prefsopen ); + return; } + data->prefs = tr_prefs_new_with_parent( data->wind ); + g_signal_connect( data->prefs, "prefs-changed", + G_CALLBACK( prefschanged ), data ); + g_signal_connect( data->prefs, "destroy", + G_CALLBACK( gtk_widget_destroyed ), &data->prefs ); + gtk_widget_show( GTK_WIDGET( data->prefs ) ); return; case ACT_DEBUG: if( !data->msgwinopen ) @@ -627,6 +907,9 @@ handleaction( struct cbdata * data, enum action act ) &data->msgwinopen ); } return; + case ACT_ICON: + remakewind( data ); + return; case ACT_START: case ACT_STOP: case ACT_DELETE: @@ -636,13 +919,7 @@ handleaction( struct cbdata * data, enum action act ) } /* get a list of references to selected rows */ - g_object_get( data->wind, "selection", &sel, NULL ); - rows = gtk_tree_selection_get_selected_rows( sel, NULL ); - for(ii = rows; NULL != ii; ii = ii->next) { - ref = gtk_tree_row_reference_new(data->model, ii->data); - gtk_tree_path_free(ii->data); - ii->data = ref; - } + rows = getselection( data ); changed = FALSE; for(ii = rows; NULL != ii; ii = ii->next) { @@ -681,6 +958,7 @@ handleaction( struct cbdata * data, enum action act ) case ACT_OPEN: case ACT_PREF: case ACT_DEBUG: + case ACT_ICON: case ACTION_COUNT: break; } @@ -699,29 +977,6 @@ handleaction( struct cbdata * data, enum action act ) } } -static const char * -defaultdir( void ) -{ - static char * wd = NULL; - const char * dir; - - dir = cf_getpref( PREF_DIR ); - if( NULL == dir ) - { - if( NULL == wd ) - { - wd = g_new( char, MAX_PATH_LENGTH + 1 ); - if( NULL == getcwd( wd, MAX_PATH_LENGTH + 1 ) ) - { - strcpy( wd, "." ); - } - } - dir = wd; - } - - return dir; -} - static void addtorrents(void *vdata, void *state, GList *files, const char *dir, guint flags) { @@ -735,20 +990,21 @@ addtorrents(void *vdata, void *state, GList *files, errlist = NULL; torlist = NULL; - if(NULL != state) - torlist = tr_backend_load_state(data->back, state, &errlist); + if( NULL != state ) + { + torlist = tr_backend_load_state( data->back, state, flags, &errlist ); + } if(NULL != files) { if( NULL == dir ) { - pref = cf_getpref( PREF_ASKDIR ); + pref = tr_prefs_get( PREF_ID_ASKDIR ); if( NULL != pref && strbool( pref ) ) { - promptfordir( data->wind, addtorrents, data, - files, flags, defaultdir() ); + promptfordir( data->wind, addtorrents, data, files, flags ); files = NULL; } - dir = defaultdir(); + dir = getdownloaddir(); } for(ii = g_list_first(files); NULL != ii; ii = ii->next) { errstr = NULL; @@ -773,9 +1029,9 @@ addtorrents(void *vdata, void *state, GList *files, if(NULL != errlist) { errstr = joinstrlist(errlist, "\n"); - errmsg(data->wind, ngettext("Failed to load torrent file:\n%s", - "Failed to load torrent files:\n%s", - g_list_length(errlist)), errstr); + errmsg( data->wind, ngettext( "Failed to load torrent file:\n%s", + "Failed to load torrent files:\n%s", + g_list_length( errlist ) ), errstr ); g_list_foreach(errlist, (GFunc)g_free, NULL); g_list_free(errlist); g_free(errstr); @@ -811,9 +1067,9 @@ safepipe(void) { static void setupsighandlers(void) { - int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2}; + int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM}; struct sigaction sa; - unsigned int ii; + int ii; bzero(&sa, sizeof(sa)); sa.sa_handler = fatalsig; diff --git a/gtk/msgwin.c b/gtk/msgwin.c index e8e394d7a..9badc6869 100644 --- a/gtk/msgwin.c +++ b/gtk/msgwin.c @@ -1,7 +1,7 @@ /****************************************************************************** * $Id$ * - * Copyright (c) 2006 Transmission authors and contributors + * Copyright (c) 2006-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"), @@ -30,6 +30,7 @@ #include "conf.h" #include "msgwin.h" +#include "tr_prefs.h" #include "transmission.h" #include "util.h" @@ -60,8 +61,7 @@ msgwin_create( void ) { GtkWidget * win, * vbox, * scroll, * text; GtkWidget * frame, * bbox, * save, * clear, * menu; PangoFontDescription * desc; - unsigned int ii; - int curlevel; + int ii, curlevel; if( NULL == textbuf ) textbuf = gtk_text_buffer_new( NULL ); @@ -126,7 +126,7 @@ changelevel( GtkWidget * widget, gpointer data SHUTUP ) { if( 0 <= index && (int) ALEN( levels ) > index && tr_getMessageLevel() != levels[index].id ) { tr_setMessageLevel( levels[index].id ); - cf_setpref( PREF_MSGLEVEL, levels[index].pref ); + cf_setpref( tr_prefs_name( PREF_ID_MSGLEVEL ), levels[index].pref ); cf_saveprefs( &ignored ); g_free( ignored ); msgwin_update(); @@ -196,10 +196,10 @@ doclear( GtkWidget * widget SHUTUP, gpointer data SHUTUP ) { void msgwin_loadpref( void ) { const char * pref; - unsigned int ii; + int ii; tr_setMessageQueuing( 1 ); - pref = cf_getpref( PREF_MSGLEVEL ); + pref = tr_prefs_get( PREF_ID_MSGLEVEL ); if( NULL == pref ) return; @@ -216,9 +216,8 @@ msgwin_update( void ) { tr_msg_list_t * msgs, * ii; GtkTextIter iter, front; char * label, * line; - int count; + int count, jj; struct tm * tm; - unsigned int jj; if( NULL == textbuf ) return; diff --git a/gtk/tr_backend.c b/gtk/tr_backend.c index cc87e3e0c..2928aec57 100644 --- a/gtk/tr_backend.c +++ b/gtk/tr_backend.c @@ -1,7 +1,7 @@ /****************************************************************************** * $Id$ * - * Copyright (c) 2006 Transmission authors and contributors + * Copyright (c) 2006-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"), @@ -220,7 +220,9 @@ tr_backend_save_state(TrBackend *back, char **errstr) { } GList * -tr_backend_load_state(TrBackend *back, benc_val_t *state, GList **errors) { +tr_backend_load_state( TrBackend * back, benc_val_t * state, + guint flags, GList ** errors ) +{ GList *ret = NULL; int ii; TrTorrent *tor; @@ -233,8 +235,8 @@ tr_backend_load_state(TrBackend *back, benc_val_t *state, GList **errors) { for(ii = 0; ii < state->val.l.count; ii++) { errstr = NULL; - tor = tr_torrent_new_with_state(G_OBJECT(back), state->val.l.vals + ii, - &errstr); + tor = tr_torrent_new_with_state( G_OBJECT( back ), state->val.l.vals + ii, + flags, &errstr ); if(NULL != errstr) *errors = g_list_append(*errors, errstr); if(NULL != tor) @@ -273,20 +275,25 @@ tr_backend_stop_torrents(TrBackend *back) { } gboolean -tr_backend_torrents_stopped(TrBackend *back) { - GList *ii, *list; - tr_stat_t *st; - gboolean ret = TRUE; +tr_backend_torrents_stopped( TrBackend * back, gboolean timeout ) +{ + GList * ii, * list; + tr_stat_t * st; + gboolean ret; - TR_IS_BACKEND(back); + TR_IS_BACKEND( back ); - list = g_list_copy(back->torrents); - for(ii = list; NULL != ii; ii = ii->next) { - st = tr_torrent_stat_polite(ii->data); - if(NULL == st || !(TR_STATUS_PAUSE & st->status)) - ret = FALSE; - } - g_list_free(list); + ret = TRUE; + list = g_list_copy( back->torrents ); + for( ii = list; NULL != ii; ii = ii->next ) + { + st = tr_torrent_stat_polite( ii->data, timeout ); + if( NULL == st || !( TR_STATUS_PAUSE & st->status ) ) + { + ret = FALSE; + } + } + g_list_free( list ); - return ret; + return ret; } diff --git a/gtk/tr_backend.h b/gtk/tr_backend.h index 102d3462d..b618745b1 100644 --- a/gtk/tr_backend.h +++ b/gtk/tr_backend.h @@ -1,7 +1,7 @@ /****************************************************************************** * $Id$ * - * Copyright (c) 2006 Transmission authors and contributors + * Copyright (c) 2006-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"), @@ -70,13 +70,14 @@ void tr_backend_save_state(TrBackend *back, char **errstr); GList * -tr_backend_load_state(TrBackend *back, benc_val_t *state, GList **errors); +tr_backend_load_state( TrBackend * back, benc_val_t * state, + guint flags, GList ** errors ); void tr_backend_stop_torrents(TrBackend *back); gboolean -tr_backend_torrents_stopped(TrBackend *back); +tr_backend_torrents_stopped( TrBackend * back, gboolean timeout ); #ifdef TR_WANT_BACKEND_PRIVATE void diff --git a/gtk/tr_icon.c b/gtk/tr_icon.c new file mode 100644 index 000000000..75f517bb1 --- /dev/null +++ b/gtk/tr_icon.c @@ -0,0 +1,293 @@ +/****************************************************************************** + * $Id$ + * + * Copyright (c) 2006-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 "tr_icon.h" +#include "util.h" + +enum +{ + PROP_ICON = 1, + PROP_DOCKED, + PROP_CLICK, +}; + +static void +tr_icon_init( GTypeInstance * instance, gpointer g_class ); +static void +tr_icon_set_property( GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec ); +static void +tr_icon_get_property( GObject * object, guint property_id, + GValue * value, GParamSpec * pspec); +static void +tr_icon_class_init( gpointer g_class, gpointer g_class_data ); +static void +tr_icon_dispose( GObject * obj ); +static void +tr_icon_finalize( GObject * obj ); +#ifdef TR_ICON_SUPPORTED +static void +clicked( GObject * obj, gpointer data ); +#endif + +GType +tr_icon_get_type( void ) +{ + static GType type = 0; + + if( 0 == type ) + { + static const GTypeInfo info = + { + sizeof( TrIconClass ), + NULL, /* base_init */ + NULL, /* base_finalize */ + tr_icon_class_init, /* class_init */ + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof( TrIcon ), + 0, /* n_preallocs */ + tr_icon_init, /* instance_init */ + NULL, + }; +#ifdef TR_ICON_SUPPORTED + type = GTK_TYPE_STATUS_ICON; +#else + type = G_TYPE_OBJECT; +#endif + type = g_type_register_static( type, "TrIcon", &info, 0 ); + } + + return type; +} + +static void +tr_icon_class_init( gpointer g_class, gpointer g_class_data SHUTUP ) +{ + GObjectClass * gobject_class; + TrIconClass * tricon_class; + GParamSpec * pspec; + + gobject_class = G_OBJECT_CLASS( g_class ); + gobject_class->set_property = tr_icon_set_property; + gobject_class->get_property = tr_icon_get_property; + gobject_class->dispose = tr_icon_dispose; + gobject_class->finalize = tr_icon_finalize; + + pspec = g_param_spec_boolean( "icon", _("Icon"), + _("Icon has been set from default window icon."), + TRUE, G_PARAM_CONSTRUCT|G_PARAM_READWRITE ); + g_object_class_install_property( gobject_class, PROP_ICON, pspec ); + + pspec = g_param_spec_boolean( "docked", _("Docked"), + _("Icon is docked in a system tray."), + FALSE, G_PARAM_READABLE ); + g_object_class_install_property( gobject_class, PROP_DOCKED, pspec ); + + pspec = g_param_spec_int( "activate-action", _("Activate action"), + _("The action id to signal when icon is activated."), + G_MININT, G_MAXINT, -1, G_PARAM_READWRITE ); + g_object_class_install_property( gobject_class, PROP_CLICK, pspec ); + + tricon_class = TR_ICON_CLASS( g_class ); + tricon_class->actionsig = + g_signal_new( "action", G_TYPE_FROM_CLASS( g_class ), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT ); +} + +static void +tr_icon_init( GTypeInstance * instance, gpointer g_class SHUTUP ) +{ + TrIcon * self = ( TrIcon * )instance; + + self->clickact = -1; + self->disposed = FALSE; + +#ifdef TR_ICON_SUPPORTED + g_signal_connect( self, "activate", G_CALLBACK( clicked ), NULL ); +#endif +} + +static void +tr_icon_set_property( GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + TrIcon * self = ( TrIcon * )object; + + if( self->disposed ) + { + return; + } + + switch( property_id ) + { + case PROP_ICON: +#ifdef TR_ICON_SUPPORTED + if( g_value_get_boolean( value ) ) + { + GList * icons = gtk_window_get_default_icon_list(); + if( NULL != icons && NULL != icons->data ) + { + gtk_status_icon_set_from_pixbuf( GTK_STATUS_ICON( self ), + icons->data ); + } + g_list_free( icons ); + } + else + { + gtk_status_icon_set_from_pixbuf( GTK_STATUS_ICON( self ), + NULL ); + } +#endif + break; + case PROP_CLICK: + self->clickact = g_value_get_int( value ); + break; + case PROP_DOCKED: + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec ); + break; + } +} + +static void +tr_icon_get_property( GObject * object, guint property_id, + GValue * value, GParamSpec * pspec ) +{ + TrIcon * self = ( TrIcon * )object; +#ifdef TR_ICON_SUPPORTED + GtkStatusIcon * icon; +#endif + + if( self->disposed ) + { + return; + } + + switch( property_id ) + { + case PROP_ICON: +#ifdef TR_ICON_SUPPORTED + icon = GTK_STATUS_ICON( self ); + if( GTK_IMAGE_PIXBUF == gtk_status_icon_get_storage_type( icon ) && + NULL != gtk_status_icon_get_pixbuf( icon ) ) + { + g_value_set_boolean( value, TRUE ); + } + else +#endif + { + g_value_set_boolean( value, FALSE ); + } + break; + case PROP_DOCKED: +#ifdef TR_ICON_SUPPORTED + if( gtk_status_icon_is_embedded( GTK_STATUS_ICON( self ) ) ) + { + g_value_set_boolean( value, TRUE ); + } + else +#endif + { + g_value_set_boolean( value, FALSE ); + } + break; + case PROP_CLICK: + g_value_set_int( value, self->clickact ); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec ); + break; + } +} + +static void +tr_icon_dispose( GObject * obj ) +{ + TrIcon * self = ( TrIcon * )obj; + GObjectClass * parent; + + if( self->disposed ) + { + return; + } + self->disposed = TRUE; + + /* Chain up to the parent class */ + parent = g_type_class_peek( g_type_parent( TR_ICON_TYPE ) ); + parent->dispose( obj ); +} + +static void +tr_icon_finalize( GObject * obj ) +{ + GObjectClass * parent; + + /* Chain up to the parent class */ + parent = g_type_class_peek( g_type_parent( TR_ICON_TYPE ) ); + parent->finalize( obj ); +} + +TrIcon * +tr_icon_new( void ) +{ + return g_object_new( TR_ICON_TYPE, NULL ); +} + +gboolean +tr_icon_docked( TrIcon * self ) +{ + gboolean ret; + + g_object_get( self, "docked", &ret, NULL ); + + return ret; +} + +#ifdef TR_ICON_SUPPORTED + +static void +clicked( GObject * obj, gpointer data SHUTUP ) +{ + TrIcon * self; + TrIconClass * class; + + TR_IS_ICON( obj ); + self = TR_ICON( obj ); + + if( self->disposed || 0 > self->clickact ) + { + return; + } + + class = g_type_class_peek( TR_ICON_TYPE ); + g_signal_emit( self, class->actionsig, 0, self->clickact ); +} + +#endif /* TR_ICON_SUPPORTED */ diff --git a/gtk/tr_icon.h b/gtk/tr_icon.h new file mode 100644 index 000000000..8de1af8ab --- /dev/null +++ b/gtk/tr_icon.h @@ -0,0 +1,89 @@ +/****************************************************************************** + * $Id$ + * + * Copyright (c) 2006-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. + *****************************************************************************/ + +#ifndef TR_ICON_H +#define TR_ICON_H + +#include + +#if GTK_MAJOR_VERSION > 2 || \ + ( GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 10 ) +#define TR_ICON_SUPPORTED +#define tr_icon_supported() (TRUE) +#else +#define tr_icon_supported() (FALSE) +#endif + +#define TR_ICON_TYPE ( tr_icon_get_type() ) + +#define TR_ICON( obj ) \ + ( G_TYPE_CHECK_INSTANCE_CAST( (obj), TR_ICON_TYPE, TrIcon ) ) + +#define TR_ICON_CLASS( class ) \ + ( G_TYPE_CHECK_CLASS_CAST( (class), TR_ICON_TYPE, TrIconClass ) ) + +#define TR_IS_ICON( obj ) \ + ( G_TYPE_CHECK_INSTANCE_TYPE( (obj), TR_ICON_TYPE ) ) + +#define TR_IS_ICON_CLASS( class ) \ + ( G_TYPE_CHECK_CLASS_TYPE( (class), TR_ICON_TYPE ) ) + +#define TR_ICON_GET_CLASS( obj ) \ + ( G_TYPE_INSTANCE_GET_CLASS( (obj), TR_ICON_TYPE, TrIconClass ) ) + +typedef struct _TrIcon TrIcon; +typedef struct _TrIconClass TrIconClass; + +/* treat the contents of this structure as private */ +struct _TrIcon +{ +#ifdef TR_ICON_SUPPORTED + GtkStatusIcon parent; +#else + GObject parent; +#endif + int clickact; + gboolean disposed; +}; + +struct _TrIconClass +{ +#ifdef TR_ICON_SUPPORTED + GtkStatusIconClass parent; +#else + GObjectClass parent; +#endif + int actionsig; +}; + +GType +tr_icon_get_type( void ); + +TrIcon * +tr_icon_new( void ); + +gboolean +tr_icon_docked( TrIcon * icon ); + +#endif diff --git a/gtk/tr_prefs.c b/gtk/tr_prefs.c new file mode 100644 index 000000000..8d623e737 --- /dev/null +++ b/gtk/tr_prefs.c @@ -0,0 +1,950 @@ +/****************************************************************************** + * $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 "tr_icon.h" +#include "tr_prefs.h" +#include "tr_torrent.h" +#include "util.h" + +#include "transmission.h" + +/* used for g_object_set/get_data */ +#define PREF_CHECK_LINK "tr-prefs-check-link-thingy" +#define PREF_SPIN_LAST "tr-prefs-spinbox-last-val" + +/* convenience macros for saving pref id on a widget */ +#define SETPREFID( wid, id ) \ + ( g_object_set_data( G_OBJECT( (wid) ), "tr-prefs-id", \ + GINT_TO_POINTER( (id) + 1 ) ) ) +#define GETPREFID( wid, id ) \ + do \ + { \ + (id) = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( (wid) ), \ + "tr-prefs-id" ) ); \ + g_assert( 0 < (id) ); \ + (id)--; \ + } \ + while( 0 ) + +enum +{ + PROP_PARENT = 1, +}; + +#define PTYPE( id ) \ + ( G_TYPE_NONE == defs[(id)]._type ? \ + defs[(id)].typefunc() : defs[(id)]._type ) + +/* please keep this in sync with the enum in tr_prefs.c */ +/* don't forget defs_int, defs_bool, and defs_file too */ +static struct +{ + char * name; + GType _type; /* don't access this directly, use PTYPE() */ + enum { PR_ENABLED, PR_DISABLED, PR_SKIP } status; + GType (*typefunc)(void); + const char * label; + const char * tip; +} +defs[] = +{ + /* PREF_ID_USEDOWNLIMIT */ + { "use-download-limit", G_TYPE_BOOLEAN, PR_ENABLED, NULL, + N_("_Limit download speed"), + N_("Restrict the download rate") }, + + /* PREF_ID_DOWNLIMIT */ + { "download-limit", G_TYPE_INT, PR_ENABLED, NULL, + N_("Maximum _download speed:"), + N_("Speed in KiB/sec for restricted download rate") }, + + /* PREF_ID_USEUPLIMIT */ + { "use-upload-limit", G_TYPE_BOOLEAN, PR_ENABLED, NULL, + N_("Li_mit upload speed"), + N_("Restrict the upload rate") }, + + /* PREF_ID_UPLIMIT */ + { "upload-limit", G_TYPE_INT, PR_ENABLED, NULL, + N_("Maximum _upload speed:"), + N_("Speed in KiB/sec for restricted upload rate") }, + + /* PREF_ID_ASKDIR */ + { "ask-download-directory", G_TYPE_BOOLEAN, PR_ENABLED, NULL, + N_("Al_ways prompt for download directory"), + N_("When adding a torrent, always prompt for a directory to download data files into") }, + + /* PREF_ID_DIR */ + { "download-directory", G_TYPE_NONE, PR_ENABLED, + gtk_file_chooser_get_type, + N_("Download di_rectory:"), + N_("Destination directory for downloaded data files") }, + + /* PREF_ID_PORT */ + { "listening-port", G_TYPE_INT, PR_ENABLED, NULL, + N_("Listening _port:"), + N_("TCP port number to listen for peer connections") }, + + /* PREF_ID_NAT */ + { "use-nat-traversal", G_TYPE_BOOLEAN, PR_ENABLED, NULL, + N_("Au_tomatic port mapping via NAT-PMP or UPnP"), + N_("Attempt to bypass NAT or firewall to allow incoming peer connections") }, + + /* PREF_ID_ICON */ + { "use-tray-icon", G_TYPE_BOOLEAN, + ( tr_icon_supported() ? PR_ENABLED : PR_DISABLED ), NULL, + N_("Display an _icon in the system tray"), + N_("Use a system tray / dock / notification area icon") }, + + /* PREF_ID_ADDSTD */ + { "add-behavior-standard", G_TYPE_NONE, PR_ENABLED, + gtk_combo_box_get_type, + N_("For torrents added _normally:"), + N_("Torrent files added via the toolbar, popup menu, and drag-and-drop") }, + + /* PREF_ID_ADDIPC */ + { "add-behavior-ipc", G_TYPE_NONE, PR_ENABLED, + gtk_combo_box_get_type, + N_("For torrents added e_xternally\n(via the command-line):"), + N_("For torrents added via the command-line only") }, + + /* PREF_ID_MSGLEVEL */ + { "message-level", G_TYPE_INT, PR_SKIP, NULL, NULL, NULL }, +}; + +static struct +{ + long min; + long max; + long def; +} +defs_int[] = +{ + { 0, 0, 0 }, + /* PREF_ID_DOWNLIMIT */ + { 0, G_MAXLONG, 100 }, + { 0, 0, 0 }, + /* PREF_ID_UPLIMIT */ + { 0, G_MAXLONG, 20 }, + { 0, 0, 0 }, { 0, 0, 0 }, + /* PREF_ID_PORT */ + { 1, 0xffff, TR_DEFAULT_PORT }, +}; + +static struct +{ + gboolean def; + int link; + gboolean enables; +} +defs_bool[] = +{ + /* PREF_ID_USEDOWNLIMIT */ + { FALSE, PREF_ID_DOWNLIMIT, TRUE }, + { FALSE, -1, FALSE }, + /* PREF_ID_USEUPLIMIT */ + { TRUE, PREF_ID_UPLIMIT, TRUE }, + { FALSE, -1, FALSE }, + /* PREF_ID_ASKDIR */ + { FALSE, PREF_ID_DIR, FALSE }, + { FALSE, -1, FALSE }, { FALSE, -1, FALSE }, + /* PREF_ID_NAT */ + { TRUE, -1, FALSE }, + /* PREF_ID_ICON */ + { TRUE, -1, FALSE }, +}; + +static struct +{ + const char * title; + GtkFileChooserAction act; + const char * (*getdef)(void); +} +defs_file[] = +{ + { NULL, 0, NULL }, { NULL, 0, NULL }, { NULL, 0, NULL }, + { NULL, 0, NULL }, { NULL, 0, NULL }, + /* PREF_ID_DIR */ + { N_("Choose a download directory"), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + getdownloaddir }, +}; + +struct checkctl +{ + GtkToggleButton * check; + GtkWidget * wids[2]; + gboolean enables; +}; + +static void +tr_prefs_init( GTypeInstance * instance, gpointer g_class ); +static void +tr_prefs_set_property( GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec ); +static void +tr_prefs_get_property( GObject * object, guint property_id, + GValue * value, GParamSpec * pspec); +static void +tr_prefs_class_init( gpointer g_class, gpointer g_class_data ); +static void +tr_prefs_dispose( GObject * obj ); +static void +gotresp( GtkWidget * widget, int resp, gpointer data ); +static int +countprefs( void ); +static void +makelinks( struct checkctl ** links ); +static void +filllinks( int id, GtkWidget * wid1, GtkWidget * wid2, + struct checkctl ** links ); +static void +pokelink( struct checkctl * link ); +static void +addwidget( TrPrefs * self, int id, GtkTable * table, int off, + GtkTooltips * tips, struct checkctl ** links ); +static GtkWidget * +tipbox( GtkWidget * widget, GtkTooltips * tips, const char * tip ); +static void +addwid_bool( TrPrefs * self, int id, GtkTooltips * tips, + GtkWidget ** wid1, struct checkctl ** links ); +static void +checkclick( GtkWidget * widget, gpointer data ); +static void +addwid_int( TrPrefs * self, int id, GtkTooltips * tips, + GtkWidget ** wid1, GtkWidget ** wid2 ); +static gboolean +spinfocus( GtkWidget * widget, GdkEventFocus *event, gpointer data ); +static void +spindie( GtkWidget * widget, gpointer data ); +static void +addwid_file( TrPrefs * self, int id, GtkTooltips * tips, + GtkWidget ** wid1, GtkWidget ** wid2 ); +static void +filechosen( GtkWidget * widget, gpointer data ); +static GtkTreeModel * +makecombomodel( void ); +static void +addwid_combo( TrPrefs * self, int id, GtkTooltips * tips, + GtkWidget ** wid1, GtkWidget ** wid2 ); +static void +combochosen( GtkWidget * widget, gpointer data ); +static void +savepref( TrPrefs * self, int id, const char * val ); + +GType +tr_prefs_get_type( void ) +{ + static GType type = 0; + + if( 0 == type ) + { + static const GTypeInfo info = + { + sizeof( TrPrefsClass ), + NULL, /* base_init */ + NULL, /* base_finalize */ + tr_prefs_class_init, /* class_init */ + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof( TrPrefs ), + 0, /* n_preallocs */ + tr_prefs_init, /* instance_init */ + NULL, + }; + type = g_type_register_static( GTK_TYPE_DIALOG, "TrPrefs", &info, 0 ); + } + + return type; +} + +static void +tr_prefs_class_init( gpointer g_class, gpointer g_class_data SHUTUP ) +{ + GObjectClass * gobject_class; + TrPrefsClass * trprefs_class; + GParamSpec * pspec; + + gobject_class = G_OBJECT_CLASS( g_class ); + gobject_class->set_property = tr_prefs_set_property; + gobject_class->get_property = tr_prefs_get_property; + gobject_class->dispose = tr_prefs_dispose; + + pspec = g_param_spec_object( "parent", _("Parent"), + _("The parent GtkWindow."), + GTK_TYPE_WINDOW, G_PARAM_READWRITE ); + g_object_class_install_property( gobject_class, PROP_PARENT, pspec ); + + trprefs_class = TR_PREFS_CLASS( g_class ); + trprefs_class->changesig = + g_signal_new( "prefs-changed", G_TYPE_FROM_CLASS( g_class ), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT ); +} + +static void +tr_prefs_init( GTypeInstance * instance, gpointer g_class SHUTUP ) +{ + struct checkctl * links[ ALEN( defs_bool ) ]; + TrPrefs * self = ( TrPrefs * )instance; + char * title; + GtkWidget * table; + GtkTooltips * tips; + int rows, ii, off; + + self->combomodel = makecombomodel(); + self->disposed = FALSE; + + title = g_strdup_printf( _("%s Preferences"), g_get_application_name() ); + gtk_window_set_title( GTK_WINDOW( self ), title ); + g_free( title ); + gtk_dialog_set_has_separator( GTK_DIALOG( self ), FALSE ); + gtk_dialog_add_button( GTK_DIALOG( self ), GTK_STOCK_CLOSE, + GTK_RESPONSE_CLOSE ); + gtk_widget_set_name( GTK_WIDGET( self ), "TransmissionDialog"); + gtk_dialog_set_default_response( GTK_DIALOG( self ), GTK_RESPONSE_CLOSE ); + gtk_container_set_border_width( GTK_CONTAINER( self ), 6 ); + gtk_window_set_resizable( GTK_WINDOW( self ), FALSE ); + + rows = countprefs(); + table = gtk_table_new( rows, 2, FALSE ); + gtk_table_set_col_spacings( GTK_TABLE( table ), 8 ); + gtk_table_set_row_spacings( GTK_TABLE( table ), 8 ); + + tips = gtk_tooltips_new(); + g_object_ref( tips ); + gtk_object_sink( GTK_OBJECT( tips ) ); + gtk_tooltips_enable( tips ); + g_signal_connect_swapped( self, "destroy", + G_CALLBACK( g_object_unref ), tips ); + + memset( links, 0, sizeof( links ) ); + makelinks( links ); + off = 0; + for( ii = 0; PREF_MAX_ID > ii; ii++ ) + { + if( PR_SKIP != defs[ii].status ) + { + addwidget( self, ii, GTK_TABLE( table ), off, tips, links ); + off++; + } + } + g_assert( rows == off ); + for( ii = 0; ALEN( links ) > ii; ii++ ) + { + g_assert( NULL == links[ii] || NULL != links[ii]->check ); + if( NULL != links[ii] ) + { + pokelink( links[ii] ); + } + } + + gtk_box_pack_start_defaults( GTK_BOX( GTK_DIALOG( self )->vbox ), table ); + g_signal_connect( self, "response", G_CALLBACK( gotresp ), NULL ); + gtk_widget_show_all( table ); +} + +static void +tr_prefs_set_property( GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + TrPrefs * self = ( TrPrefs * )object; + + if( self->disposed ) + { + return; + } + + switch( property_id ) + { + case PROP_PARENT: + gtk_window_set_transient_for( GTK_WINDOW( self ), + g_value_get_object( value ) ); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec ); + break; + } +} + +static void +tr_prefs_get_property( GObject * object, guint property_id, + GValue * value, GParamSpec * pspec ) +{ + TrPrefs * self = ( TrPrefs * )object; + GtkWindow * trans; + + if( self->disposed ) + { + return; + } + + switch( property_id ) + { + case PROP_PARENT: + trans = gtk_window_get_transient_for( GTK_WINDOW( self ) ); + g_value_set_object( value, trans ); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec ); + break; + } +} + +static void +tr_prefs_dispose( GObject * obj ) +{ + TrPrefs * self = ( TrPrefs * )obj; + GObjectClass * parent; + + if( self->disposed ) + { + return; + } + self->disposed = TRUE; + + g_object_unref( self->combomodel ); + + /* Chain up to the parent class */ + parent = g_type_class_peek( g_type_parent( TR_PREFS_TYPE ) ); + parent->dispose( obj ); +} + +TrPrefs * +tr_prefs_new( void ) +{ + return g_object_new( TR_PREFS_TYPE, NULL ); +} + +TrPrefs * +tr_prefs_new_with_parent( GtkWindow * parent ) +{ + return g_object_new( TR_PREFS_TYPE, "parent", parent, NULL ); +} + +const char * +tr_prefs_name( int id ) +{ + g_assert( 0 <= id && PREF_MAX_ID > id && ALEN( defs ) == PREF_MAX_ID ); + return defs[id].name; +} + +gboolean +tr_prefs_get_int( int id, int * val ) +{ + const char * str; + char * end; + int ret; + + str = tr_prefs_get( id ); + if( NULL == str || '\0' == *str ) + { + return FALSE; + } + + errno = 0; + ret = strtol( str, &end, 10 ); + if( 0 != errno || NULL == end || '\0' != *end ) + { + return FALSE; + } + *val = ret; + return TRUE; +} + +gboolean +tr_prefs_get_bool( int id, gboolean * val ) +{ + const char * str; + + str = tr_prefs_get( id ); + if( NULL == str ) + { + return FALSE; + } + *val = strbool( str ); + return TRUE; +} + +int +tr_prefs_get_int_with_default( int id ) +{ + int ret; + + g_assert( 0 <= id && ALEN( defs ) > id && + G_TYPE_INT == PTYPE( id ) && ALEN( defs_int ) > id ); + + if( tr_prefs_get_int( id, &ret ) ) + { + return ret; + } + return defs_int[id].def; +} + +gboolean +tr_prefs_get_bool_with_default( int id ) +{ + gboolean ret; + + g_assert( 0 <= id && ALEN( defs ) > id && + G_TYPE_BOOLEAN == PTYPE( id ) && ALEN( defs_bool ) > id ); + + if( tr_prefs_get_bool( id, &ret ) ) + { + return ret; + } + return defs_bool[id].def; + +} + +static void +gotresp( GtkWidget * widget, int resp SHUTUP, gpointer data SHUTUP ) +{ + gtk_widget_destroy( widget ); +} + +static int +countprefs( void ) +{ + int ii, ret; + + g_assert( ALEN( defs ) == PREF_MAX_ID ); + ret = 0; + for( ii = 0; PREF_MAX_ID > ii; ii++ ) + { + if( PR_SKIP != defs[ii].status ) + { + ret++; + } + } + + return ret; +} + +static void +makelinks( struct checkctl ** links ) +{ + int ii; + + g_assert( ALEN( defs ) == PREF_MAX_ID ); + for( ii = 0; PREF_MAX_ID > ii; ii++ ) + { + if( PR_SKIP == defs[ii].status || G_TYPE_BOOLEAN != PTYPE( ii ) ) + { + continue; + } + g_assert( ALEN( defs_bool ) > ii ); + if( 0 <= defs_bool[ii].link ) + { + links[ii] = g_new0( struct checkctl, 1 ); + } + } +} + +static void +filllinks( int id, GtkWidget * wid1, GtkWidget * wid2, + struct checkctl ** links ) +{ + int ii; + + g_assert( ALEN( defs ) >= ALEN( defs_bool ) ); + for( ii = 0; ALEN( defs_bool) > ii; ii++ ) + { + if( NULL == links[ii] ) + { + g_assert( PR_SKIP == defs[ii].status || + G_TYPE_BOOLEAN != PTYPE( ii ) || + 0 > defs_bool[ii].link ); + } + else + { + g_assert( PR_SKIP != defs[ii].status && + G_TYPE_BOOLEAN == PTYPE( ii ) && + 0 <= defs_bool[ii].link ); + if( id == defs_bool[ii].link ) + { + links[ii]->wids[0] = wid1; + links[ii]->wids[1] = wid2; + } + } + } +} + +static void +pokelink( struct checkctl * link ) +{ + gboolean active; + + active = gtk_toggle_button_get_active( link->check ); + active = ( link->enables ? active : !active ); + gtk_widget_set_sensitive( link->wids[0], active ); + gtk_widget_set_sensitive( link->wids[1], active ); +} + +static void +addwidget( TrPrefs * self, int id, GtkTable * table, int off, + GtkTooltips * tips, struct checkctl ** links ) +{ + GType type; + GtkWidget * add1, * add2; + + g_assert( ALEN( defs ) > id ); + + type = PTYPE( id ); + add1 = NULL; + add2 = NULL; + if( G_TYPE_BOOLEAN == type ) + { + addwid_bool( self, id, tips, &add1, links ); + } + else if( G_TYPE_INT == type ) + { + addwid_int( self, id, tips, &add1, &add2 ); + } + else if( GTK_TYPE_FILE_CHOOSER == type ) + { + addwid_file( self, id, tips, &add1, &add2 ); + } + else if( GTK_TYPE_COMBO_BOX == type ) + { + addwid_combo( self, id, tips, &add1, &add2 ); + } + else + { + g_assert_not_reached(); + } + + g_assert( NULL != add1 ); + filllinks( id, add1, add2, links ); + if( NULL == add2 ) + { + gtk_table_attach_defaults( table, add1, 0, 2, off, off + 1 ); + } + else + { + gtk_table_attach_defaults( table, add1, 0, 1, off, off + 1 ); + gtk_table_attach_defaults( table, add2, 1, 2, off, off + 1 ); + } + if( PR_DISABLED == defs[id].status ) + { + gtk_widget_set_sensitive( add1, FALSE ); + if( NULL != add2 ) + { + gtk_widget_set_sensitive( add2, FALSE ); + } + } +} + +/* wrap a widget in an event box with a tooltip */ +static GtkWidget * +tipbox( GtkWidget * widget, GtkTooltips * tips, const char * tip ) +{ + GtkWidget * box; + + box = gtk_event_box_new(); + gtk_container_add( GTK_CONTAINER( box ), widget ); + gtk_tooltips_set_tip( tips, box, tip, "" ); + + return box; +} + +static void +addwid_bool( TrPrefs * self, int id, GtkTooltips * tips, + GtkWidget ** wid1, struct checkctl ** links ) +{ + GtkWidget * check; + gboolean active; + + g_assert( ALEN( defs ) > id && G_TYPE_BOOLEAN == PTYPE( id ) ); + check = gtk_check_button_new_with_mnemonic( gettext( defs[id].label ) ); + gtk_tooltips_set_tip( tips, check, gettext( defs[id].tip ), "" ); + if( 0 > defs_bool[id].link ) + { + g_assert( NULL == links[id] ); + } + else + { + links[id]->check = GTK_TOGGLE_BUTTON( check ); + links[id]->enables = defs_bool[id].enables; + g_object_set_data_full( G_OBJECT( check ), PREF_CHECK_LINK, + links[id], g_free ); + } + active = tr_prefs_get_bool_with_default( id ); + gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check ), active ); + SETPREFID( check, id ); + g_signal_connect( check, "clicked", G_CALLBACK( checkclick ), self ); + + *wid1 = check; +} + +static void +checkclick( GtkWidget * widget, gpointer data ) +{ + TrPrefs * self; + struct checkctl * link; + int id; + gboolean active; + + TR_IS_PREFS( data ); + self = TR_PREFS( data ); + link = g_object_get_data( G_OBJECT( widget ), PREF_CHECK_LINK ); + GETPREFID( widget, id ); + + if( NULL != link ) + { + pokelink( link ); + } + + active = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( widget ) ); + savepref( self, id, ( active ? "yes" : "no" ) ); +} + +static void +addwid_int( TrPrefs * self, int id, GtkTooltips * tips, + GtkWidget ** wid1, GtkWidget ** wid2 ) +{ + GtkWidget * spin, * label; + int val, * last; + + g_assert( ALEN( defs ) > id && G_TYPE_INT == PTYPE( id ) ); + spin = gtk_spin_button_new_with_range( defs_int[id].min, + defs_int[id].max, 1 ); + label = gtk_label_new_with_mnemonic( gettext( defs[id].label ) ); + gtk_label_set_mnemonic_widget( GTK_LABEL( label ), spin ); + gtk_misc_set_alignment( GTK_MISC( label ), 0, .5 ); + gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin ), TRUE ); + gtk_tooltips_set_tip( tips, spin, gettext( defs[id].tip ), "" ); + val = tr_prefs_get_int_with_default( id ); + gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin ), val ); + last = g_new( int, 1 ); + *last = val; + g_object_set_data_full( G_OBJECT( spin ), PREF_SPIN_LAST, last, g_free ); + SETPREFID( spin, id ); + /* I don't trust that focus-out-event will always work, + so save pref on widget destruction too */ + g_signal_connect( spin, "focus-out-event", G_CALLBACK( spinfocus ), self ); + g_signal_connect( spin, "destroy", G_CALLBACK( spindie ), self ); + + *wid1 = tipbox( label, tips, gettext( defs[id].tip ) ); + *wid2 = spin; +} + +static gboolean +spinfocus( GtkWidget * widget, GdkEventFocus *event SHUTUP, gpointer data ) +{ + TrPrefs * self; + int * last, id, cur; + char * str; + + TR_IS_PREFS( data ); + self = TR_PREFS( data ); + last = g_object_get_data( G_OBJECT( widget ), PREF_SPIN_LAST ); + GETPREFID( widget, id ); + cur = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( widget ) ); + + if( cur != *last ) + { + str = g_strdup_printf( "%i", cur ); + savepref( self, id, str ); + g_free( str ); + *last = cur; + } + + /* continue propagating the event */ + return FALSE; +} + +static void +spindie( GtkWidget * widget, gpointer data ) +{ + spinfocus( widget, NULL, data ); +} + +static void +addwid_file( TrPrefs * self, int id, GtkTooltips * tips, + GtkWidget ** wid1, GtkWidget ** wid2 ) +{ + GtkWidget * file, * label; + const char * pref; + + g_assert( ALEN( defs ) > id && GTK_TYPE_FILE_CHOOSER == PTYPE( id ) ); + file = gtk_file_chooser_button_new( gettext( defs_file[id].title ), + defs_file[id].act ); + label = gtk_label_new_with_mnemonic( gettext( defs[id].label ) ); + gtk_label_set_mnemonic_widget( GTK_LABEL( label ), file ); + gtk_misc_set_alignment( GTK_MISC( label ), 0, .5 ); + pref = tr_prefs_get( id ); + if( NULL == pref ) + { + pref = defs_file[id].getdef(); + } + gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( file ), pref ); + SETPREFID( file, id ); + g_signal_connect( file, "selection-changed", + G_CALLBACK( filechosen ), self ); + + *wid1 = tipbox( label, tips, gettext( defs[id].tip ) ); + *wid2 = tipbox( file, tips, gettext( defs[id].tip ) ); +} + +static void +filechosen( GtkWidget * widget, gpointer data ) +{ + TrPrefs * self; + const char * dir; + int id; + + TR_IS_PREFS( data ); + self = TR_PREFS( data ); + dir = gtk_file_chooser_get_current_folder( GTK_FILE_CHOOSER( widget ) ); + GETPREFID( widget, id ); + savepref( self, id, dir ); +} + +static GtkTreeModel * +makecombomodel( void ) +{ + GtkListStore * list; + GtkTreeIter iter; + + /* create the model used by the two popup menus */ + list = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_INT ); + gtk_list_store_append( list, &iter ); + gtk_list_store_set( list, &iter, 1, 0, 0, + _("Use the torrent file where it is"), -1 ); + gtk_list_store_append( list, &iter ); + gtk_list_store_set( list, &iter, 1, TR_TORNEW_SAVE_COPY, 0, + _("Keep a copy of the torrent file"), -1 ); + gtk_list_store_append( list, &iter ); + gtk_list_store_set( list, &iter, 1, TR_TORNEW_SAVE_MOVE, 0, + _("Keep a copy and remove the original"), -1 ); + + return GTK_TREE_MODEL( list ); +} + +static void +addwid_combo( TrPrefs * self, int id, GtkTooltips * tips, + GtkWidget ** wid1, GtkWidget ** wid2 ) +{ + GtkWidget * combo, * label; + GtkCellRenderer * rend; + GtkTreeIter iter; + guint prefsflag, modelflag; + + g_assert( ALEN( defs ) > id && GTK_TYPE_COMBO_BOX == PTYPE( id ) ); + combo = gtk_combo_box_new(); + label = gtk_label_new_with_mnemonic( gettext( defs[id].label ) ); + gtk_label_set_mnemonic_widget( GTK_LABEL( label ), combo ); + gtk_misc_set_alignment( GTK_MISC( label ), 0, .5 ); + gtk_combo_box_set_model( GTK_COMBO_BOX( combo ), self->combomodel ); + rend = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( combo ), rend, TRUE ); + gtk_cell_layout_add_attribute( GTK_CELL_LAYOUT( combo ), rend, "text", 0 ); + + prefsflag = addactionflag( tr_prefs_get( id ) ); + if( gtk_tree_model_get_iter_first( self->combomodel, &iter ) ) + { + do + { + gtk_tree_model_get( self->combomodel, &iter, 1, &modelflag, -1 ); + if( modelflag == prefsflag) + { + gtk_combo_box_set_active_iter( GTK_COMBO_BOX( combo ), &iter ); + break; + } + } + while( gtk_tree_model_iter_next( self->combomodel, &iter ) ); + } + SETPREFID( combo, id ); + g_signal_connect( combo, "changed", G_CALLBACK( combochosen ), self ); + + *wid1 = tipbox( label, tips, gettext( defs[id].tip ) ); + *wid2 = tipbox( combo, tips, gettext( defs[id].tip ) ); +} + +static void +combochosen( GtkWidget * widget, gpointer data ) +{ + TrPrefs * self; + GtkTreeIter iter; + GtkTreeModel * model; + guint flags; + int id; + + TR_IS_PREFS( data ); + self = TR_PREFS( data ); + if( gtk_combo_box_get_active_iter( GTK_COMBO_BOX( widget ), &iter ) ) + { + model = gtk_combo_box_get_model( GTK_COMBO_BOX( widget ) ); + gtk_tree_model_get( model, &iter, 1, &flags, -1 ); + GETPREFID( widget, id ); + savepref( self, id, addactionname( flags ) ); + } +} + +static void +savepref( TrPrefs * self, int id, const char * val ) +{ + const char * name, * old; + char * errstr; + TrPrefsClass * class; + + name = tr_prefs_name( id ); + old = cf_getpref( name ); + if( NULL == old ) + { + if( old == val ) + { + return; + } + } + else + { + if( 0 == strcmp( old, val ) ) + { + return; + } + } + cf_setpref( name, val ); + + /* write prefs to disk */ + cf_saveprefs( &errstr ); + if( NULL != errstr ) + { + errmsg( GTK_WINDOW( self ), "%s", errstr ); + g_free( errstr ); + } + + /* signal a pref change */ + class = g_type_class_peek( TR_PREFS_TYPE ); + g_signal_emit( self, class->changesig, 0, id ); +} diff --git a/gtk/tr_prefs.h b/gtk/tr_prefs.h new file mode 100644 index 000000000..424e9dc1c --- /dev/null +++ b/gtk/tr_prefs.h @@ -0,0 +1,111 @@ +/****************************************************************************** + * $Id$ + * + * Copyright (c) 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. + *****************************************************************************/ + +#ifndef TR_PREFS_H +#define TR_PREFS_H + +#include + +#include "conf.h" + +#define TR_PREFS_TYPE ( tr_prefs_get_type() ) + +#define TR_PREFS( obj ) \ + ( G_TYPE_CHECK_INSTANCE_CAST( (obj), TR_PREFS_TYPE, TrPrefs ) ) + +#define TR_PREFS_CLASS( class ) \ + ( G_TYPE_CHECK_CLASS_CAST( (class), TR_PREFS_TYPE, TrPrefsClass ) ) + +#define TR_IS_PREFS( obj ) \ + ( G_TYPE_CHECK_INSTANCE_TYPE( (obj), TR_PREFS_TYPE ) ) + +#define TR_IS_PREFS_CLASS( class ) \ + ( G_TYPE_CHECK_CLASS_TYPE( (class), TR_PREFS_TYPE ) ) + +#define TR_PREFS_GET_CLASS( obj ) \ + ( G_TYPE_INSTANCE_GET_CLASS( (obj), TR_PREFS_TYPE, TrPrefsClass ) ) + +typedef struct _TrPrefs TrPrefs; +typedef struct _TrPrefsClass TrPrefsClass; + +/* treat the contents of this structure as private */ +struct _TrPrefs +{ + GtkDialog parent; + GtkTreeModel * combomodel; + gboolean disposed; +}; + +struct _TrPrefsClass +{ + GtkDialogClass parent; + int changesig; +}; + +GType +tr_prefs_get_type( void ); + +TrPrefs * +tr_prefs_new( void ); + +TrPrefs * +tr_prefs_new_with_parent( GtkWindow * parent ); + +/* please keep this in sync with defs in tr_prefs.c */ +enum +{ + PREF_ID_USEDOWNLIMIT = 0, + PREF_ID_DOWNLIMIT, + PREF_ID_USEUPLIMIT, + PREF_ID_UPLIMIT, + PREF_ID_ASKDIR, + PREF_ID_DIR, + PREF_ID_PORT, + PREF_ID_NAT, + PREF_ID_ICON, + PREF_ID_ADDSTD, + PREF_ID_ADDIPC, + PREF_ID_MSGLEVEL, + PREF_MAX_ID +}; + +const char * +tr_prefs_name( int id ); + +/* convenience macros and functions for reading pref by id */ +#define tr_prefs_get( id ) cf_getpref( tr_prefs_name( (id) ) ) + +gboolean +tr_prefs_get_int( int id, int * val ); + +gboolean +tr_prefs_get_bool( int id, gboolean * val ); + +int +tr_prefs_get_int_with_default( int id ); + +gboolean +tr_prefs_get_bool_with_default( int id ); + +#endif diff --git a/gtk/tr_torrent.c b/gtk/tr_torrent.c index 1bbfffcdf..188330982 100644 --- a/gtk/tr_torrent.c +++ b/gtk/tr_torrent.c @@ -308,7 +308,9 @@ tr_torrent_new(GObject *backend, const char *torrent, const char *dir, } TrTorrent * -tr_torrent_new_with_state(GObject *backend, benc_val_t *state, char **err) { +tr_torrent_new_with_state( GObject * backend, benc_val_t * state, + guint forcedflags, char ** err) +{ int ii; benc_val_t *name, *data; char *torrent, *hash, *dir; @@ -353,6 +355,12 @@ tr_torrent_new_with_state(GObject *backend, benc_val_t *state, char **err) { flags |= TR_TORNEW_LOAD_SAVED; torrent = hash; } + forcedflags &= TR_TORNEW_PAUSED | TR_TORNEW_RUNNING; + if( forcedflags ) + { + flags &= ~( TR_TORNEW_PAUSED | TR_TORNEW_RUNNING ); + flags |= forcedflags; + } return tr_torrent_new(backend, torrent, dir, flags, err); } @@ -448,20 +456,26 @@ tr_torrent_stop_politely(TrTorrent *tor) { } tr_stat_t * -tr_torrent_stat_polite(TrTorrent *tor) { - TrTorrentClass *klass; - tr_stat_t *st; +tr_torrent_stat_polite( TrTorrent * tor, gboolean timeout ) +{ + TrTorrentClass * klass; + tr_stat_t * st; - if(tor->disposed) - return NULL; + TR_IS_TORRENT( tor ); - st = tr_torrentStat(tor->handle); - if(tor->closing && TR_STATUS_PAUSE & st->status) { - tor->closing = FALSE; - klass = g_type_class_peek(TR_TORRENT_TYPE); - g_signal_emit(tor, klass->paused_signal_id, 0, NULL); - return tr_torrent_stat_polite(tor); - } + if( tor->disposed ) + { + return NULL; + } - return st; + st = tr_torrentStat( tor->handle ); + if( tor->closing && ( TR_STATUS_PAUSE & st->status || timeout ) ) + { + tor->closing = FALSE; + klass = g_type_class_peek( TR_TORRENT_TYPE ); + g_signal_emit( tor, klass->paused_signal_id, 0, NULL ); + return tr_torrent_stat_polite( tor, FALSE ); + } + + return st; } diff --git a/gtk/tr_torrent.h b/gtk/tr_torrent.h index d5ac962cb..8c5c25db3 100644 --- a/gtk/tr_torrent.h +++ b/gtk/tr_torrent.h @@ -1,7 +1,7 @@ /****************************************************************************** * $Id$ * - * Copyright (c) 2006 Transmission authors and contributors + * Copyright (c) 2006-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"), @@ -88,13 +88,14 @@ tr_torrent_new(GObject *backend, const char *torrent, const char *dir, guint flags, char **err); TrTorrent * -tr_torrent_new_with_state(GObject *backend, benc_val_t *state, char **err); +tr_torrent_new_with_state( GObject * backend, benc_val_t * state, + guint flags, char ** err ); void tr_torrent_stop_politely(TrTorrent *tor); tr_stat_t * -tr_torrent_stat_polite(TrTorrent *tor); +tr_torrent_stat_polite( TrTorrent * tor, gboolean timeout ); #ifdef TR_WANT_TORRENT_PRIVATE void diff --git a/gtk/tr_window.h b/gtk/tr_window.h index 13c29866b..e6ddd4ec3 100644 --- a/gtk/tr_window.h +++ b/gtk/tr_window.h @@ -90,33 +90,8 @@ void tr_window_update( TrWindow * wind, float downspeed, float upspeed ); /* some evil magic to show the window with a nice initial window size */ +/* note that the gtk main loop runs in this function */ void tr_window_size_hack( TrWindow * wind ); -/* XXX these should be somewhere else */ -#define ACTF_TOOL ( 1 << 0 ) /* appear in the toolbar */ -#define ACTF_MENU ( 1 << 1 ) /* appear in the popup menu */ -#define ACTF_ALWAYS ( 1 << 2 ) /* available regardless of selection */ -#define ACTF_ACTIVE ( 1 << 3 ) /* available for active torrent */ -#define ACTF_INACTIVE ( 1 << 4 ) /* available for inactive torrent */ -/* appear in the toolbar and the popup menu */ -#define ACTF_WHEREVER ( ACTF_TOOL | ACTF_MENU ) -/* available if there is something selected */ -#define ACTF_WHATEVER ( ACTF_ACTIVE | ACTF_INACTIVE ) - -/* XXX this too*/ -#define ACT_ISAVAIL( flags, status ) \ - ( ( ACTF_ACTIVE & (flags) && TR_STATUS_ACTIVE & (status) ) || \ - ( ACTF_INACTIVE & (flags) && TR_STATUS_INACTIVE & (status) ) || \ - ACTF_ALWAYS & (flags) ) - -/* XXX and this */ -/* model column names */ -enum { - MC_NAME, MC_SIZE, MC_STAT, MC_ERR, MC_TERR, - MC_PROG, MC_DRATE, MC_URATE, MC_ETA, MC_PEERS, - MC_UPEERS, MC_DPEERS, MC_DOWN, MC_UP, - MC_TORRENT, MC_ROW_COUNT, -}; - #endif diff --git a/gtk/transmission-gtk.desktop b/gtk/transmission-gtk.desktop index 13d0ee676..00050f6ee 100644 --- a/gtk/transmission-gtk.desktop +++ b/gtk/transmission-gtk.desktop @@ -3,9 +3,8 @@ Encoding=UTF-8 Version=1.0 Name=Transmission Name[ru]=Передача -GenericName=BitTorrent Client Type=Application -Comment=A free, lightweight client with a simple, intuitive interface +Comment=A free, lightweight BitTorrent client with a simple, intuitive interface Exec=transmission-gtk %F TryExec=transmission-gtk Icon=transmission.png diff --git a/gtk/util.c b/gtk/util.c index 103499bbd..d6d1abcf5 100644 --- a/gtk/util.c +++ b/gtk/util.c @@ -1,7 +1,7 @@ /****************************************************************************** * $Id$ * - * Copyright (c) 2005-2006 Transmission authors and contributors + * 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"), @@ -33,6 +33,7 @@ #include #include +#include "tr_prefs.h" #include "tr_torrent.h" #include "util.h" @@ -65,7 +66,7 @@ static const char *sizestrs[] = { char * readablesize(guint64 size) { - unsigned int ii; + int ii; double small = size; for(ii = 0; ii + 1 < ALEN(sizestrs) && 1024.0 <= small / 1024.0; ii++) @@ -308,34 +309,68 @@ makeglist(void *ptr, ...) { return ret; } -GtkWidget * -errmsg(GtkWindow *wind, const char *format, ...) { - GtkWidget *dialog; - va_list ap; +const char * +getdownloaddir( void ) +{ + static char * wd = NULL; + const char * dir; - va_start(ap, format); - dialog = verrmsg(wind, NULL, NULL, format, ap); - va_end(ap); + dir = tr_prefs_get( PREF_ID_DIR ); + if( NULL == dir ) + { + if( NULL == wd ) + { + wd = g_new( char, MAX_PATH_LENGTH + 1 ); + if( NULL == getcwd( wd, MAX_PATH_LENGTH + 1 ) ) + { + strcpy( wd, "." ); + } + } + dir = wd; + } - return dialog; + return dir; +} + +void +errmsg( GtkWindow * wind, const char * format, ... ) +{ + GtkWidget * dialog; + va_list ap; + + va_start( ap, format ); + dialog = verrmsg_full( wind, NULL, NULL, format, ap ); + va_end( ap ); + + if( NULL != wind && !GTK_WIDGET_MAPPED( GTK_WIDGET( wind ) ) ) + { + g_signal_connect_swapped( wind, "map", + G_CALLBACK( gtk_widget_show ), dialog ); + } + else + { + gtk_widget_show( dialog ); + } } GtkWidget * -errmsg_full(GtkWindow *wind, callbackfunc_t func, void *data, - const char *format, ...) { - GtkWidget *dialog; - va_list ap; +errmsg_full( GtkWindow * wind, callbackfunc_t func, void * data, + const char * format, ... ) +{ + GtkWidget * dialog; + va_list ap; - va_start(ap, format); - dialog = verrmsg(wind, func, data, format, ap); - va_end(ap); + va_start( ap, format ); + dialog = verrmsg_full( wind, func, data, format, ap ); + va_end( ap ); - return dialog; + return dialog; } GtkWidget * -verrmsg(GtkWindow *wind, callbackfunc_t func, void *data, - const char *format, va_list ap) { +verrmsg_full( GtkWindow * wind, callbackfunc_t func, void * data, + const char * format, va_list ap ) +{ GtkWidget *dialog; char *msg; GList *funcdata; @@ -355,8 +390,6 @@ verrmsg(GtkWindow *wind, callbackfunc_t func, void *data, else funcdata = g_list_append(g_list_append(NULL, func), data); g_signal_connect(dialog, "response", G_CALLBACK(errcb), funcdata); - if(NULL != wind) - gtk_widget_show(dialog); g_free(msg); return dialog; diff --git a/gtk/util.h b/gtk/util.h index 96205ebc6..616142d87 100644 --- a/gtk/util.h +++ b/gtk/util.h @@ -38,13 +38,36 @@ typedef void (*add_torrents_func_t)(void*,void*,GList*,const char*,guint); /* return number of items in array */ -#define ALEN(a) (sizeof(a) / sizeof((a)[0])) - -#define ISA(o, t) (g_type_is_a(G_OBJECT_TYPE(G_OBJECT(o)), (t))) +#define ALEN( a ) ( ( signed )( sizeof(a) / sizeof( (a)[0] ) ) ) /* used for a callback function with a data parameter */ typedef void (*callbackfunc_t)(void*); +/* flags indicating where and when an action is valid */ +#define ACTF_TOOL ( 1 << 0 ) /* appear in the toolbar */ +#define ACTF_MENU ( 1 << 1 ) /* appear in the popup menu */ +#define ACTF_ALWAYS ( 1 << 2 ) /* available regardless of selection */ +#define ACTF_ACTIVE ( 1 << 3 ) /* available for active torrent */ +#define ACTF_INACTIVE ( 1 << 4 ) /* available for inactive torrent */ +/* appear in the toolbar and the popup menu */ +#define ACTF_WHEREVER ( ACTF_TOOL | ACTF_MENU ) +/* available if there is something selected */ +#define ACTF_WHATEVER ( ACTF_ACTIVE | ACTF_INACTIVE ) + +/* checking action flags against torrent status */ +#define ACT_ISAVAIL( flags, status ) \ + ( ( ACTF_ACTIVE & (flags) && TR_STATUS_ACTIVE & (status) ) || \ + ( ACTF_INACTIVE & (flags) && TR_STATUS_INACTIVE & (status) ) || \ + ACTF_ALWAYS & (flags) ) + +/* column names for the model used to store torrent information */ +enum { + MC_NAME, MC_SIZE, MC_STAT, MC_ERR, MC_TERR, + MC_PROG, MC_DRATE, MC_URATE, MC_ETA, MC_PEERS, + MC_UPEERS, MC_DPEERS, MC_DOWN, MC_UP, + MC_TORRENT, MC_ROW_COUNT, +}; + /* try to interpret a string as a textual representation of a boolean */ /* note that this isn't localized */ gboolean @@ -101,28 +124,35 @@ addactionname(guint flag); GList * makeglist(void *ptr, ...); +/* retrieve the global download directory */ +const char * +getdownloaddir( void ); + #ifdef GTK_MAJOR_VERSION -/* if wind is NULL then you must call gtk_widget_show on the returned widget */ - -GtkWidget * -errmsg(GtkWindow *wind, const char *format, ...) +/* create an error dialog, if wind is NULL or mapped then show dialog now, + otherwise show it when wind becomes mapped */ +void +errmsg( GtkWindow * wind, const char * format, ... ) #ifdef __GNUC__ - __attribute__ ((format (printf, 2, 3))) + __attribute__ (( format ( printf, 2, 3 ) )) #endif - ; + ; +/* create an error dialog but do not gtk_widget_show() it, + calls func( data ) when the dialog is closed */ GtkWidget * -errmsg_full(GtkWindow *wind, callbackfunc_t func, void *data, - const char *format, ...) +errmsg_full( GtkWindow * wind, callbackfunc_t func, void * data, + const char * format, ... ) #ifdef __GNUC__ - __attribute__ ((format (printf, 4, 5))) + __attribute__ (( format ( printf, 4, 5 ) )) #endif - ; + ; +/* varargs version of errmsg_full() */ GtkWidget * -verrmsg(GtkWindow *wind, callbackfunc_t func, void *data, - const char *format, va_list ap); +verrmsg_full( GtkWindow * wind, callbackfunc_t func, void * data, + const char * format, va_list ap ); #endif /* GTK_MAJOR_VERSION */ diff --git a/mk/gtk.mk b/mk/gtk.mk index 5367d77ad..2930d29fd 100644 --- a/mk/gtk.mk +++ b/mk/gtk.mk @@ -3,8 +3,9 @@ include ../mk/config.mk include ../mk/common.mk -SRCS = conf.c dialogs.c io.c ipc.c main.c msgwin.c tr_backend.c tr_torrent.c \ - tr_cell_renderer_progress.c tr_window.c util.c +SRCS = conf.c dialogs.c io.c ipc.c main.c msgwin.c tr_backend.c \ + tr_cell_renderer_progress.c tr_icon.c tr_prefs.c tr_torrent.c \ + tr_window.c util.c OBJS = $(SRCS:%.c=%.o) CFLAGS += $(CFLAGS_GTK) -I../libtransmission