1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-23 16:24:02 +00:00
transmission/gtk/tr_prefs.c

1023 lines
30 KiB
C

/******************************************************************************
* $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_core.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,
PROP_CORE,
};
#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_PEX */
{ "use-peer-exchange", G_TYPE_BOOLEAN, PR_ENABLED, NULL,
N_("Use peer _exchange if possible"),
N_("Perform Azureus or \xc2\xb5Torrent compatible peer exchange with any peers which support it") },
/* 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_ASKQUIT */
{ "ask-quit", G_TYPE_BOOLEAN, PR_ENABLED, NULL,
N_("Confirm _quit"),
N_("Prompt for confirmation when quitting") },
/* 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_PEX */
{ TRUE, -1, FALSE },
/* PREF_ID_ICON */
{ TRUE, -1, FALSE },
/* PREF_ID_ASKQUIT */
{ 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, GtkWidget ** wids );
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, GtkWidget ** wids );
static void
checkclick( GtkWidget * widget, gpointer data );
static void
addwid_int( TrPrefs * self, int id, GtkTooltips * tips,
GtkWidget ** wid1, GtkWidget ** wid2, GtkWidget ** wids );
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, GtkWidget ** wids );
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, GtkWidget ** wids );
static void
setcombosel( GtkTreeModel * model, GtkComboBox * combo, int id );
static void
combochosen( GtkWidget * widget, gpointer data );
static void
prefschanged( TrCore * core, int id, gpointer data );
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;
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 );
pspec = g_param_spec_object( "core", "Parent",
"The TrCore object.",
TR_CORE_TYPE, G_PARAM_READWRITE );
g_object_class_install_property( gobject_class, PROP_CORE, pspec );
}
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->core = NULL;
self->prefwids = g_new0( GtkWidget *, PREF_MAX_ID );
self->disposed = FALSE;
self->fileselhack = FALSE;
gtk_window_set_role( GTK_WINDOW( self ), "tr-prefs" );
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,
self->prefwids );
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;
case PROP_CORE:
if( NULL != self->core )
{
g_signal_handler_disconnect( self->core, self->sigid );
g_object_unref( self->core );
}
self->core = g_value_get_object( value );
if( NULL != self->core )
{
g_object_ref( self->core );
self->sigid = g_signal_connect( self->core, "prefs-changed",
G_CALLBACK( prefschanged ),
self );
}
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;
case PROP_CORE:
g_value_set_object( value, self->core );
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 );
if( NULL != self->core )
{
g_signal_handler_disconnect( self->core, self->sigid );
g_object_unref( self->core );
}
g_free( self->prefwids );
/* Chain up to the parent class */
parent = g_type_class_peek( g_type_parent( TR_PREFS_TYPE ) );
parent->dispose( obj );
}
TrPrefs *
tr_prefs_new( GObject * core )
{
return g_object_new( TR_PREFS_TYPE, "core", core, NULL );
}
TrPrefs *
tr_prefs_new_with_parent( GObject * core, GtkWindow * parent )
{
return g_object_new( TR_PREFS_TYPE, "core", core, "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, GtkWidget ** widgets )
{
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, widgets );
}
else if( G_TYPE_INT == type )
{
addwid_int( self, id, tips, &add1, &add2, widgets );
}
else if( GTK_TYPE_FILE_CHOOSER == type )
{
addwid_file( self, id, tips, &add1, &add2, widgets );
}
else if( GTK_TYPE_COMBO_BOX == type )
{
addwid_combo( self, id, tips, &add1, &add2, widgets );
}
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 ** wids )
{
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 ) );
wids[id] = check;
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 ) );
tr_core_set_pref_bool( TR_CORE( self->core ), id, active );
}
static void
addwid_int( TrPrefs * self, int id, GtkTooltips * tips,
GtkWidget ** wid1, GtkWidget ** wid2, GtkWidget ** wids )
{
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 );
wids[id] = spin;
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;
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 )
{
tr_core_set_pref_int( TR_CORE( self->core ), id, cur );
*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 ** wids )
{
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 );
wids[id] = file;
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;
char * dir;
int id;
TR_IS_PREFS( data );
self = TR_PREFS( data );
dir = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( widget ) );
GETPREFID( widget, id );
self->fileselhack = TRUE;
tr_core_set_pref( TR_CORE( self->core ), id, dir );
self->fileselhack = FALSE;
g_free( 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, TR_TOR_LEAVE, 0,
_("Use the torrent file where it is"), -1 );
gtk_list_store_append( list, &iter );
gtk_list_store_set( list, &iter, 1, TR_TOR_COPY, 0,
_("Keep a copy of the torrent file"), -1 );
gtk_list_store_append( list, &iter );
gtk_list_store_set( list, &iter, 1, TR_TOR_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 ** wids )
{
GtkWidget * combo, * label;
GtkCellRenderer * rend;
g_assert( ALEN( defs ) > id && GTK_TYPE_COMBO_BOX == PTYPE( id ) );
combo = gtk_combo_box_new();
wids[id] = combo;
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 );
setcombosel( self->combomodel, GTK_COMBO_BOX( combo ), id );
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
setcombosel( GtkTreeModel * model, GtkComboBox * combo, int id )
{
GtkTreeIter iter;
enum tr_torrent_action prefsact, modelact;
prefsact = toraddaction( tr_prefs_get( id ) );
if( gtk_tree_model_get_iter_first( model, &iter ) )
{
do
{
gtk_tree_model_get( model, &iter, 1, &modelact, -1 );
if( modelact == prefsact )
{
gtk_combo_box_set_active_iter( combo, &iter );
break;
}
}
while( gtk_tree_model_iter_next( model, &iter ) );
}
}
static void
combochosen( GtkWidget * widget, gpointer data )
{
TrPrefs * self;
GtkTreeIter iter;
GtkTreeModel * model;
enum tr_torrent_action action;
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, &action, -1 );
GETPREFID( widget, id );
tr_core_set_pref( TR_CORE( self->core ), id, toractionname( action ) );
}
}
static void
prefschanged( TrCore * core SHUTUP, int id, gpointer data )
{
TrPrefs * prefs;
GtkWidget * wid;
gboolean boolval;
int intval;
const char * strval;
TR_IS_PREFS( data );
prefs = TR_PREFS( data );
g_assert( ALEN( defs ) > id && 0 <= id );
wid = prefs->prefwids[id];
if( G_TYPE_BOOLEAN == PTYPE( id ) )
{
boolval = tr_prefs_get_bool_with_default( id );
if( boolval !=
gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( wid ) ) )
{
gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( wid ), boolval );
}
}
else if( G_TYPE_INT == PTYPE( id ) )
{
intval = tr_prefs_get_int_with_default( id );
if( intval !=
gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( wid ) ) )
{
gtk_spin_button_set_value( GTK_SPIN_BUTTON( wid ), intval );
}
}
else if( GTK_TYPE_FILE_CHOOSER == PTYPE( id ) )
{
strval = tr_prefs_get( id );
if( !prefs->fileselhack && strval !=
gtk_file_chooser_get_current_folder( GTK_FILE_CHOOSER( wid ) ) )
{
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( wid ),
strval );
}
}
else if( GTK_TYPE_COMBO_BOX == PTYPE( id ) )
{
setcombosel( prefs->combomodel, GTK_COMBO_BOX( wid ), id );
}
else
{
g_assert_not_reached();
}
}