Merge gtkmisc branch.

This commit is contained in:
Josh Elsasser 2007-02-19 22:09:05 +00:00
parent c7e9f024e0
commit 4943058f72
21 changed files with 2320 additions and 884 deletions

View File

@ -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 );
}
}

View File

@ -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 */

View File

@ -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 );
}

View File

@ -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 */

138
gtk/ipc.c
View File

@ -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);
}

View File

@ -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 */

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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;
}

View File

@ -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

293
gtk/tr_icon.c Normal file
View File

@ -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 <gtk/gtk.h>
#include <glib/gi18n.h>
#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 */

89
gtk/tr_icon.h Normal file
View File

@ -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 <gtk/gtk.h>
#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

950
gtk/tr_prefs.c Normal file
View File

@ -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 <errno.h>
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#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 );
}

111
gtk/tr_prefs.h Normal file
View File

@ -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 <gtk/gtk.h>
#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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <gtk/gtk.h>
#include <glib/gi18n.h>
#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;

View File

@ -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 */

View File

@ -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