source: trunk/gtk/tr_prefs.c @ 1504

Last change on this file since 1504 was 1504, checked in by joshe, 15 years ago

Merge gtkmisc branch.

  • Property svn:keywords set to Date Rev Author Id
File size: 27.1 KB
Line 
1/******************************************************************************
2 * $Id: tr_prefs.c 1504 2007-02-19 22:09:05Z joshe $
3 *
4 * Copyright (c) 2005-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <errno.h>
26#include <stdlib.h>
27#include <string.h>
28
29#include <gtk/gtk.h>
30#include <glib/gi18n.h>
31
32#include "conf.h"
33#include "tr_icon.h"
34#include "tr_prefs.h"
35#include "tr_torrent.h"
36#include "util.h"
37
38#include "transmission.h"
39
40/* used for g_object_set/get_data */
41#define PREF_CHECK_LINK         "tr-prefs-check-link-thingy"
42#define PREF_SPIN_LAST          "tr-prefs-spinbox-last-val"
43
44/* convenience macros for saving pref id on a widget */
45#define SETPREFID( wid, id )                                                  \
46    ( g_object_set_data( G_OBJECT( (wid) ), "tr-prefs-id",                    \
47                         GINT_TO_POINTER( (id) + 1 ) ) )
48#define GETPREFID( wid, id )                                                  \
49    do                                                                        \
50    {                                                                         \
51        (id) = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( (wid) ),         \
52                                                   "tr-prefs-id" ) );         \
53        g_assert( 0 < (id) );                                                 \
54        (id)--;                                                               \
55    }                                                                         \
56    while( 0 )
57
58enum
59{
60    PROP_PARENT = 1,
61};
62
63#define PTYPE( id )                                                           \
64    ( G_TYPE_NONE == defs[(id)]._type ?                                       \
65      defs[(id)].typefunc() : defs[(id)]._type )
66
67/* please keep this in sync with the enum in tr_prefs.c */
68/* don't forget defs_int, defs_bool, and defs_file too */
69static struct
70{
71    char       * name;
72    GType        _type;         /* don't access this directly, use PTYPE() */
73    enum { PR_ENABLED, PR_DISABLED, PR_SKIP } status;
74    GType (*typefunc)(void);
75    const char * label;
76    const char * tip;
77}
78defs[] =
79{
80    /* PREF_ID_USEDOWNLIMIT */
81    { "use-download-limit",     G_TYPE_BOOLEAN, PR_ENABLED,  NULL,
82      N_("_Limit download speed"),
83      N_("Restrict the download rate") },
84
85    /* PREF_ID_DOWNLIMIT */
86    { "download-limit",         G_TYPE_INT,     PR_ENABLED,  NULL,
87      N_("Maximum _download speed:"),
88      N_("Speed in KiB/sec for restricted download rate") },
89
90    /* PREF_ID_USEUPLIMIT */
91    { "use-upload-limit",       G_TYPE_BOOLEAN, PR_ENABLED,  NULL,
92      N_("Li_mit upload speed"),
93      N_("Restrict the upload rate") },
94
95    /* PREF_ID_UPLIMIT */
96    { "upload-limit",           G_TYPE_INT,     PR_ENABLED,  NULL,
97      N_("Maximum _upload speed:"),
98      N_("Speed in KiB/sec for restricted upload rate") },
99
100    /* PREF_ID_ASKDIR */
101    { "ask-download-directory", G_TYPE_BOOLEAN, PR_ENABLED,  NULL,
102      N_("Al_ways prompt for download directory"),
103      N_("When adding a torrent, always prompt for a directory to download data files into") },
104
105    /* PREF_ID_DIR */
106    { "download-directory",     G_TYPE_NONE,   PR_ENABLED,
107      gtk_file_chooser_get_type,
108      N_("Download di_rectory:"),
109      N_("Destination directory for downloaded data files") },
110
111    /* PREF_ID_PORT */
112    { "listening-port",         G_TYPE_INT,     PR_ENABLED,  NULL,
113      N_("Listening _port:"),
114      N_("TCP port number to listen for peer connections") },
115
116    /* PREF_ID_NAT */
117    { "use-nat-traversal",      G_TYPE_BOOLEAN, PR_ENABLED,  NULL,
118      N_("Au_tomatic port mapping via NAT-PMP or UPnP"),
119      N_("Attempt to bypass NAT or firewall to allow incoming peer connections") },
120
121    /* PREF_ID_ICON */
122    { "use-tray-icon",          G_TYPE_BOOLEAN,
123      ( tr_icon_supported() ? PR_ENABLED : PR_DISABLED ),    NULL,
124      N_("Display an _icon in the system tray"),
125      N_("Use a system tray / dock / notification area icon") },
126
127    /* PREF_ID_ADDSTD */
128    { "add-behavior-standard",  G_TYPE_NONE,    PR_ENABLED,
129      gtk_combo_box_get_type,
130      N_("For torrents added _normally:"),
131      N_("Torrent files added via the toolbar, popup menu, and drag-and-drop") },
132
133    /* PREF_ID_ADDIPC */
134    { "add-behavior-ipc",       G_TYPE_NONE,    PR_ENABLED,
135      gtk_combo_box_get_type,
136      N_("For torrents added e_xternally\n(via the command-line):"),
137      N_("For torrents added via the command-line only") },
138
139    /* PREF_ID_MSGLEVEL */
140    { "message-level",          G_TYPE_INT,     PR_SKIP, NULL, NULL, NULL },
141};
142
143static struct
144{
145    long min;
146    long max;
147    long def;
148}
149defs_int[] = 
150{
151    { 0, 0, 0 },
152    /* PREF_ID_DOWNLIMIT */
153    { 0, G_MAXLONG, 100 },
154    { 0, 0, 0 },
155    /* PREF_ID_UPLIMIT */
156    { 0, G_MAXLONG, 20 },
157    { 0, 0, 0 }, { 0, 0, 0 },
158    /* PREF_ID_PORT */
159    { 1, 0xffff,    TR_DEFAULT_PORT },
160};
161
162static struct
163{
164    gboolean def;
165    int      link;
166    gboolean enables;
167}
168defs_bool[] = 
169{
170    /* PREF_ID_USEDOWNLIMIT */
171    { FALSE, PREF_ID_DOWNLIMIT, TRUE },
172    { FALSE, -1, FALSE },
173    /* PREF_ID_USEUPLIMIT */
174    { TRUE,  PREF_ID_UPLIMIT,   TRUE },
175    { FALSE, -1, FALSE },
176    /* PREF_ID_ASKDIR */
177    { FALSE, PREF_ID_DIR,       FALSE },
178    { FALSE, -1, FALSE }, { FALSE, -1, FALSE },
179    /* PREF_ID_NAT */
180    { TRUE,  -1,                FALSE },
181    /* PREF_ID_ICON */
182    { TRUE,  -1,                FALSE },
183};
184
185static struct
186{
187    const char         * title;
188    GtkFileChooserAction act;
189    const char * (*getdef)(void);
190}
191defs_file[] = 
192{
193    { NULL, 0, NULL }, { NULL, 0, NULL }, { NULL, 0, NULL },
194    { NULL, 0, NULL }, { NULL, 0, NULL },
195    /* PREF_ID_DIR */
196    { N_("Choose a download directory"),
197      GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
198      getdownloaddir },
199};
200
201struct checkctl
202{
203    GtkToggleButton * check;
204    GtkWidget       * wids[2];
205    gboolean          enables;
206};
207
208static void
209tr_prefs_init( GTypeInstance * instance, gpointer g_class );
210static void
211tr_prefs_set_property( GObject * object, guint property_id,
212                      const GValue * value, GParamSpec * pspec );
213static void
214tr_prefs_get_property( GObject * object, guint property_id,
215                      GValue * value, GParamSpec * pspec);
216static void
217tr_prefs_class_init( gpointer g_class, gpointer g_class_data );
218static void
219tr_prefs_dispose( GObject * obj );
220static void
221gotresp( GtkWidget * widget, int resp, gpointer data );
222static int
223countprefs( void );
224static void
225makelinks( struct checkctl ** links );
226static void
227filllinks( int id, GtkWidget * wid1, GtkWidget * wid2,
228           struct checkctl ** links );
229static void
230pokelink( struct checkctl * link );
231static void
232addwidget( TrPrefs * self, int id, GtkTable * table, int off,
233           GtkTooltips * tips, struct checkctl ** links );
234static GtkWidget *
235tipbox( GtkWidget * widget, GtkTooltips * tips, const char * tip );
236static void
237addwid_bool( TrPrefs * self, int id, GtkTooltips * tips,
238             GtkWidget ** wid1, struct checkctl ** links );
239static void
240checkclick( GtkWidget * widget, gpointer data );
241static void
242addwid_int( TrPrefs * self, int id, GtkTooltips * tips,
243            GtkWidget ** wid1, GtkWidget ** wid2 );
244static gboolean
245spinfocus( GtkWidget * widget, GdkEventFocus *event, gpointer data );
246static void
247spindie( GtkWidget * widget, gpointer data );
248static void
249addwid_file( TrPrefs * self, int id, GtkTooltips * tips,
250             GtkWidget ** wid1, GtkWidget ** wid2 );
251static void
252filechosen( GtkWidget * widget, gpointer data );
253static GtkTreeModel *
254makecombomodel( void );
255static void
256addwid_combo( TrPrefs * self, int id, GtkTooltips * tips,
257              GtkWidget ** wid1, GtkWidget ** wid2 );
258static void
259combochosen( GtkWidget * widget, gpointer data );
260static void
261savepref( TrPrefs * self, int id, const char * val );
262
263GType
264tr_prefs_get_type( void )
265{
266    static GType type = 0;
267
268    if( 0 == type )
269    {
270        static const GTypeInfo info =
271        {
272            sizeof( TrPrefsClass ),
273            NULL,                       /* base_init */
274            NULL,                       /* base_finalize */
275            tr_prefs_class_init,        /* class_init */
276            NULL,                       /* class_finalize */
277            NULL,                       /* class_data */
278            sizeof( TrPrefs ),
279            0,                          /* n_preallocs */
280            tr_prefs_init,              /* instance_init */
281            NULL,
282        };
283        type = g_type_register_static( GTK_TYPE_DIALOG, "TrPrefs", &info, 0 );
284    }
285
286    return type;
287}
288
289static void
290tr_prefs_class_init( gpointer g_class, gpointer g_class_data SHUTUP )
291{
292    GObjectClass * gobject_class;
293    TrPrefsClass  * trprefs_class;
294    GParamSpec   * pspec;
295
296    gobject_class = G_OBJECT_CLASS( g_class );
297    gobject_class->set_property = tr_prefs_set_property;
298    gobject_class->get_property = tr_prefs_get_property;
299    gobject_class->dispose      = tr_prefs_dispose;
300
301    pspec = g_param_spec_object( "parent", _("Parent"),
302                                 _("The parent GtkWindow."),
303                                 GTK_TYPE_WINDOW, G_PARAM_READWRITE );
304    g_object_class_install_property( gobject_class, PROP_PARENT, pspec );
305
306    trprefs_class = TR_PREFS_CLASS( g_class );
307    trprefs_class->changesig =
308        g_signal_new( "prefs-changed", G_TYPE_FROM_CLASS( g_class ),
309                       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
310                       g_cclosure_marshal_VOID__INT,
311                       G_TYPE_NONE, 1, G_TYPE_INT );
312}
313
314static void
315tr_prefs_init( GTypeInstance * instance, gpointer g_class SHUTUP )
316{
317    struct checkctl * links[ ALEN( defs_bool ) ];
318    TrPrefs     * self = ( TrPrefs * )instance;
319    char        * title;
320    GtkWidget   * table;
321    GtkTooltips * tips;
322    int           rows, ii, off;
323
324    self->combomodel = makecombomodel();
325    self->disposed   = FALSE;
326
327    title = g_strdup_printf( _("%s Preferences"), g_get_application_name() );
328    gtk_window_set_title( GTK_WINDOW( self ), title );
329    g_free( title );
330    gtk_dialog_set_has_separator( GTK_DIALOG( self ), FALSE );
331    gtk_dialog_add_button( GTK_DIALOG( self ), GTK_STOCK_CLOSE,
332                           GTK_RESPONSE_CLOSE );
333    gtk_widget_set_name( GTK_WIDGET( self ), "TransmissionDialog");
334    gtk_dialog_set_default_response( GTK_DIALOG( self ), GTK_RESPONSE_CLOSE );
335    gtk_container_set_border_width( GTK_CONTAINER( self ), 6 );
336    gtk_window_set_resizable( GTK_WINDOW( self ), FALSE );
337
338    rows = countprefs();
339    table = gtk_table_new( rows, 2, FALSE );
340    gtk_table_set_col_spacings( GTK_TABLE( table ), 8 );
341    gtk_table_set_row_spacings( GTK_TABLE( table ), 8 );
342
343    tips = gtk_tooltips_new();
344    g_object_ref( tips );
345    gtk_object_sink( GTK_OBJECT( tips ) );
346    gtk_tooltips_enable( tips );
347    g_signal_connect_swapped( self, "destroy",
348                              G_CALLBACK( g_object_unref ), tips );
349
350    memset( links, 0, sizeof( links ) );
351    makelinks( links );
352    off = 0;
353    for( ii = 0; PREF_MAX_ID > ii; ii++ )
354    {
355        if( PR_SKIP != defs[ii].status )
356        {
357            addwidget( self, ii, GTK_TABLE( table ), off, tips, links );
358            off++;
359        }
360    }
361    g_assert( rows == off );
362    for( ii = 0; ALEN( links ) > ii; ii++ )
363    {
364        g_assert( NULL == links[ii] || NULL != links[ii]->check );
365        if( NULL != links[ii] )
366        {
367            pokelink( links[ii] );
368        }
369    }
370
371    gtk_box_pack_start_defaults( GTK_BOX( GTK_DIALOG( self )->vbox ), table );
372    g_signal_connect( self, "response", G_CALLBACK( gotresp ), NULL );
373    gtk_widget_show_all( table );
374}
375
376static void
377tr_prefs_set_property( GObject * object, guint property_id,
378                      const GValue * value, GParamSpec * pspec)
379{
380    TrPrefs * self = ( TrPrefs * )object;
381
382    if( self->disposed )
383    {
384        return;
385    }
386
387    switch( property_id )
388    {
389        case PROP_PARENT:
390            gtk_window_set_transient_for( GTK_WINDOW( self ),
391                                          g_value_get_object( value ) );
392            break;
393        default:
394            G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
395            break;
396    }
397}
398
399static void
400tr_prefs_get_property( GObject * object, guint property_id,
401                      GValue * value, GParamSpec * pspec )
402{
403    TrPrefs   * self = ( TrPrefs * )object;
404    GtkWindow * trans;
405
406    if( self->disposed )
407    {
408        return;
409    }
410
411    switch( property_id )
412    {
413        case PROP_PARENT:
414            trans = gtk_window_get_transient_for( GTK_WINDOW( self ) );
415            g_value_set_object( value, trans );
416            break;
417        default:
418            G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
419            break;
420    }
421}
422
423static void
424tr_prefs_dispose( GObject * obj )
425{
426    TrPrefs      * self = ( TrPrefs * )obj;
427    GObjectClass * parent;
428
429    if( self->disposed )
430    {
431        return;
432    }
433    self->disposed = TRUE;
434
435    g_object_unref( self->combomodel );
436
437    /* Chain up to the parent class */
438    parent = g_type_class_peek( g_type_parent( TR_PREFS_TYPE ) );
439    parent->dispose( obj );
440}
441
442TrPrefs *
443tr_prefs_new( void )
444{
445    return g_object_new( TR_PREFS_TYPE, NULL );
446}
447
448TrPrefs *
449tr_prefs_new_with_parent( GtkWindow * parent )
450{
451    return g_object_new( TR_PREFS_TYPE, "parent", parent, NULL );
452}
453
454const char *
455tr_prefs_name( int id )
456{
457    g_assert( 0 <= id && PREF_MAX_ID > id  && ALEN( defs ) == PREF_MAX_ID );
458    return defs[id].name;
459}
460
461gboolean
462tr_prefs_get_int( int id, int * val )
463{
464    const char * str;
465    char       * end;
466    int          ret;
467
468    str = tr_prefs_get( id );
469    if( NULL == str || '\0' == *str )
470    {
471        return FALSE;
472    }
473
474    errno = 0;
475    ret = strtol( str, &end, 10 );
476    if( 0 != errno || NULL == end || '\0' != *end )
477    {
478        return FALSE;
479    }
480    *val = ret;
481    return TRUE;
482}
483
484gboolean
485tr_prefs_get_bool( int id, gboolean * val )
486{
487    const char * str;
488
489    str = tr_prefs_get( id );
490    if( NULL == str )
491    {
492        return FALSE;
493    }
494    *val = strbool( str );
495    return TRUE;
496}
497
498int
499tr_prefs_get_int_with_default( int id )
500{
501    int ret;
502
503    g_assert( 0 <= id && ALEN( defs ) > id &&
504              G_TYPE_INT == PTYPE( id ) && ALEN( defs_int ) > id );
505
506    if( tr_prefs_get_int( id, &ret ) )
507    {
508        return ret;
509    }
510    return defs_int[id].def;
511}
512
513gboolean
514tr_prefs_get_bool_with_default( int id )
515{
516    gboolean ret;
517
518    g_assert( 0 <= id && ALEN( defs ) > id &&
519              G_TYPE_BOOLEAN == PTYPE( id ) && ALEN( defs_bool ) > id );
520
521    if( tr_prefs_get_bool( id, &ret ) )
522    {
523        return ret;
524    }
525    return defs_bool[id].def;
526
527}
528
529static void
530gotresp( GtkWidget * widget, int resp SHUTUP, gpointer data SHUTUP )
531{
532    gtk_widget_destroy( widget );
533}
534
535static int
536countprefs( void )
537{
538    int ii, ret;
539
540    g_assert( ALEN( defs ) == PREF_MAX_ID );
541    ret = 0;
542    for( ii = 0; PREF_MAX_ID > ii; ii++ )
543    {
544        if( PR_SKIP != defs[ii].status )
545        {
546            ret++;
547        }
548    }
549
550    return ret;
551}
552
553static void
554makelinks( struct checkctl ** links )
555{
556    int ii;
557
558    g_assert( ALEN( defs ) == PREF_MAX_ID );
559    for( ii = 0; PREF_MAX_ID > ii; ii++ )
560    {
561        if( PR_SKIP == defs[ii].status || G_TYPE_BOOLEAN != PTYPE( ii ) )
562        {
563            continue;
564        }
565        g_assert( ALEN( defs_bool ) > ii );
566        if( 0 <= defs_bool[ii].link )
567        {
568            links[ii] = g_new0( struct checkctl, 1 );
569        }
570    }
571}
572
573static void
574filllinks( int id, GtkWidget * wid1, GtkWidget * wid2,
575           struct checkctl ** links )
576{
577    int ii;
578
579    g_assert( ALEN( defs ) >= ALEN( defs_bool ) );
580    for( ii = 0; ALEN( defs_bool) > ii; ii++ )
581    {
582        if( NULL == links[ii] )
583        {
584            g_assert( PR_SKIP == defs[ii].status ||
585                      G_TYPE_BOOLEAN != PTYPE( ii ) ||
586                      0 > defs_bool[ii].link );
587        }
588        else
589        {
590            g_assert( PR_SKIP != defs[ii].status &&
591                      G_TYPE_BOOLEAN == PTYPE( ii ) &&
592                      0 <= defs_bool[ii].link );
593            if( id == defs_bool[ii].link )
594            {
595                links[ii]->wids[0] = wid1;
596                links[ii]->wids[1] = wid2;
597            }
598        }
599    }
600}
601
602static void
603pokelink( struct checkctl * link )
604{
605    gboolean active;
606
607    active = gtk_toggle_button_get_active( link->check );
608    active = ( link->enables ? active : !active );
609    gtk_widget_set_sensitive( link->wids[0], active );
610    gtk_widget_set_sensitive( link->wids[1], active );
611}
612
613static void
614addwidget( TrPrefs * self, int id, GtkTable * table, int off,
615           GtkTooltips * tips, struct checkctl ** links )
616{
617    GType       type;
618    GtkWidget * add1, * add2;
619
620    g_assert( ALEN( defs ) > id );
621
622    type = PTYPE( id );
623    add1 = NULL;
624    add2 = NULL;
625    if( G_TYPE_BOOLEAN == type )
626    {
627        addwid_bool( self, id, tips, &add1, links );
628    }
629    else if( G_TYPE_INT == type )
630    {
631        addwid_int( self, id, tips, &add1, &add2 );
632    }
633    else if( GTK_TYPE_FILE_CHOOSER == type )
634    {
635        addwid_file( self, id, tips, &add1, &add2 );
636    }
637    else if( GTK_TYPE_COMBO_BOX == type )
638    {
639        addwid_combo( self, id, tips, &add1, &add2 );
640    }
641    else
642    {
643        g_assert_not_reached();
644    }
645
646    g_assert( NULL != add1 );
647    filllinks( id, add1, add2, links );
648    if( NULL == add2 )
649    {
650        gtk_table_attach_defaults( table, add1, 0, 2, off, off + 1 );
651    }
652    else
653    {
654        gtk_table_attach_defaults( table, add1, 0, 1, off, off + 1 );
655        gtk_table_attach_defaults( table, add2, 1, 2, off, off + 1 );
656    }
657    if( PR_DISABLED == defs[id].status )
658    {
659        gtk_widget_set_sensitive( add1, FALSE );
660        if( NULL != add2 )
661        {
662            gtk_widget_set_sensitive( add2, FALSE );
663        }
664    }
665}
666
667/* wrap a widget in an event box with a tooltip */
668static GtkWidget *
669tipbox( GtkWidget * widget, GtkTooltips * tips, const char * tip )
670{
671    GtkWidget * box;
672
673    box = gtk_event_box_new();
674    gtk_container_add( GTK_CONTAINER( box ), widget );
675    gtk_tooltips_set_tip( tips, box, tip, "" );
676
677    return box;
678}
679
680static void
681addwid_bool( TrPrefs * self, int id, GtkTooltips * tips,
682             GtkWidget ** wid1, struct checkctl ** links )
683{
684    GtkWidget  * check;
685    gboolean     active;
686
687    g_assert( ALEN( defs ) > id && G_TYPE_BOOLEAN == PTYPE( id ) );
688    check = gtk_check_button_new_with_mnemonic( gettext( defs[id].label ) );
689    gtk_tooltips_set_tip( tips, check, gettext( defs[id].tip ), "" );
690    if( 0 > defs_bool[id].link )
691    {
692        g_assert( NULL == links[id] );
693    }
694    else
695    {
696        links[id]->check = GTK_TOGGLE_BUTTON( check );
697        links[id]->enables = defs_bool[id].enables;
698        g_object_set_data_full( G_OBJECT( check ), PREF_CHECK_LINK,
699                                links[id], g_free );
700    }
701    active = tr_prefs_get_bool_with_default( id );
702    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check ), active );
703    SETPREFID( check, id );
704    g_signal_connect( check, "clicked", G_CALLBACK( checkclick ), self );
705
706    *wid1 = check;
707}
708
709static void
710checkclick( GtkWidget * widget, gpointer data )
711{
712    TrPrefs         * self;
713    struct checkctl * link;
714    int               id;
715    gboolean          active;
716
717    TR_IS_PREFS( data );
718    self = TR_PREFS( data );
719    link = g_object_get_data( G_OBJECT( widget ), PREF_CHECK_LINK );
720    GETPREFID( widget, id );
721
722    if( NULL != link )
723    {
724        pokelink( link );
725    }
726
727    active = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( widget ) );
728    savepref( self, id, ( active ? "yes" : "no" ) );
729}
730
731static void
732addwid_int( TrPrefs * self, int id, GtkTooltips * tips,
733            GtkWidget ** wid1, GtkWidget ** wid2 )
734{
735    GtkWidget * spin, * label;
736    int         val, * last;
737
738    g_assert( ALEN( defs ) > id && G_TYPE_INT == PTYPE( id ) );
739    spin = gtk_spin_button_new_with_range( defs_int[id].min,
740                                           defs_int[id].max, 1 );
741    label = gtk_label_new_with_mnemonic( gettext( defs[id].label ) );
742    gtk_label_set_mnemonic_widget( GTK_LABEL( label ), spin );
743    gtk_misc_set_alignment( GTK_MISC( label ), 0, .5 );
744    gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin ), TRUE );
745    gtk_tooltips_set_tip( tips, spin, gettext( defs[id].tip ), "" );
746    val = tr_prefs_get_int_with_default( id );
747    gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin ), val );
748    last = g_new( int, 1 );
749    *last = val;
750    g_object_set_data_full( G_OBJECT( spin ), PREF_SPIN_LAST, last, g_free );
751    SETPREFID( spin, id );
752    /* I don't trust that focus-out-event will always work,
753       so save pref on widget destruction too */
754    g_signal_connect( spin, "focus-out-event", G_CALLBACK( spinfocus ), self );
755    g_signal_connect( spin, "destroy", G_CALLBACK( spindie ), self );
756
757    *wid1 = tipbox( label, tips, gettext( defs[id].tip ) );
758    *wid2 = spin;
759}
760
761static gboolean
762spinfocus( GtkWidget * widget, GdkEventFocus *event SHUTUP, gpointer data )
763{
764    TrPrefs * self;
765    int     * last, id, cur;
766    char    * str;
767
768    TR_IS_PREFS( data );
769    self = TR_PREFS( data );
770    last = g_object_get_data( G_OBJECT( widget ), PREF_SPIN_LAST );
771    GETPREFID( widget, id );
772    cur = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( widget ) );
773
774    if( cur != *last )
775    {
776        str = g_strdup_printf( "%i", cur );
777        savepref( self, id, str );
778        g_free( str );
779        *last = cur;
780    }
781
782    /* continue propagating the event */
783    return FALSE;
784}
785
786static void
787spindie( GtkWidget * widget, gpointer data )
788{
789    spinfocus( widget, NULL, data );
790}
791
792static void
793addwid_file( TrPrefs * self, int id, GtkTooltips * tips,
794             GtkWidget ** wid1, GtkWidget ** wid2 )
795{
796    GtkWidget  * file, * label;
797    const char * pref;
798
799    g_assert( ALEN( defs ) > id && GTK_TYPE_FILE_CHOOSER == PTYPE( id ) );
800    file = gtk_file_chooser_button_new( gettext( defs_file[id].title ),
801                                        defs_file[id].act );
802    label = gtk_label_new_with_mnemonic( gettext( defs[id].label ) );
803    gtk_label_set_mnemonic_widget( GTK_LABEL( label ), file );
804    gtk_misc_set_alignment( GTK_MISC( label ), 0, .5 );
805    pref = tr_prefs_get( id );
806    if( NULL == pref )
807    {
808        pref = defs_file[id].getdef();
809    }
810    gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( file ), pref );
811    SETPREFID( file, id );
812    g_signal_connect( file, "selection-changed",
813                      G_CALLBACK( filechosen ), self );
814
815    *wid1 = tipbox( label, tips, gettext( defs[id].tip ) );
816    *wid2 = tipbox( file,  tips, gettext( defs[id].tip ) );
817}
818
819static void
820filechosen( GtkWidget * widget, gpointer data )
821{
822    TrPrefs    * self;
823    const char * dir;
824    int          id;
825
826    TR_IS_PREFS( data );
827    self = TR_PREFS( data );
828    dir = gtk_file_chooser_get_current_folder( GTK_FILE_CHOOSER( widget ) );
829    GETPREFID( widget, id );
830    savepref( self, id, dir );
831}
832
833static GtkTreeModel *
834makecombomodel( void )
835{
836    GtkListStore * list;
837    GtkTreeIter    iter;
838
839    /* create the model used by the two popup menus */
840    list = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_INT );
841    gtk_list_store_append( list, &iter );
842    gtk_list_store_set( list, &iter, 1, 0, 0,
843                        _("Use the torrent file where it is"), -1 );
844    gtk_list_store_append( list, &iter );
845    gtk_list_store_set( list, &iter, 1, TR_TORNEW_SAVE_COPY, 0,
846                        _("Keep a copy of the torrent file"), -1 );
847    gtk_list_store_append( list, &iter );
848    gtk_list_store_set( list, &iter, 1, TR_TORNEW_SAVE_MOVE, 0,
849                        _("Keep a copy and remove the original"), -1 );
850
851    return GTK_TREE_MODEL( list );
852}
853
854static void
855addwid_combo( TrPrefs * self, int id, GtkTooltips * tips,
856              GtkWidget ** wid1, GtkWidget ** wid2 )
857{
858    GtkWidget       * combo, * label;
859    GtkCellRenderer * rend;
860    GtkTreeIter       iter;
861    guint             prefsflag, modelflag;
862
863    g_assert( ALEN( defs ) > id && GTK_TYPE_COMBO_BOX == PTYPE( id ) );
864    combo = gtk_combo_box_new();
865    label = gtk_label_new_with_mnemonic( gettext( defs[id].label ) );
866    gtk_label_set_mnemonic_widget( GTK_LABEL( label ), combo );
867    gtk_misc_set_alignment( GTK_MISC( label ), 0, .5 );
868    gtk_combo_box_set_model( GTK_COMBO_BOX( combo ), self->combomodel );
869    rend = gtk_cell_renderer_text_new();
870    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( combo ), rend, TRUE );
871    gtk_cell_layout_add_attribute( GTK_CELL_LAYOUT( combo ), rend, "text", 0 );
872
873    prefsflag = addactionflag( tr_prefs_get( id ) );
874    if( gtk_tree_model_get_iter_first( self->combomodel, &iter ) )
875    {
876        do
877        {
878            gtk_tree_model_get( self->combomodel, &iter, 1, &modelflag, -1 );
879            if( modelflag == prefsflag)
880            {
881                gtk_combo_box_set_active_iter( GTK_COMBO_BOX( combo ), &iter );
882                break;
883            }
884        }
885        while( gtk_tree_model_iter_next( self->combomodel, &iter ) );
886    }
887    SETPREFID( combo, id );
888    g_signal_connect( combo, "changed", G_CALLBACK( combochosen ), self );
889
890    *wid1 = tipbox( label, tips, gettext( defs[id].tip ) );
891    *wid2 = tipbox( combo, tips, gettext( defs[id].tip ) );
892}
893
894static void
895combochosen( GtkWidget * widget, gpointer data )
896{
897    TrPrefs      * self;
898    GtkTreeIter    iter;
899    GtkTreeModel * model;
900    guint          flags;
901    int            id;
902
903    TR_IS_PREFS( data );
904    self = TR_PREFS( data );
905    if( gtk_combo_box_get_active_iter( GTK_COMBO_BOX( widget ), &iter ) )
906    {
907        model = gtk_combo_box_get_model( GTK_COMBO_BOX( widget ) );
908        gtk_tree_model_get( model, &iter, 1, &flags, -1 );
909        GETPREFID( widget, id );
910        savepref( self, id, addactionname( flags ) );
911    }
912}
913
914static void
915savepref( TrPrefs * self, int id, const char * val )
916{
917    const char   * name, * old;
918    char         * errstr;
919    TrPrefsClass * class;
920
921    name = tr_prefs_name( id );
922    old = cf_getpref( name );
923    if( NULL == old )
924    {
925        if( old == val )
926        {
927            return;
928        }
929    }
930    else
931    {
932        if( 0 == strcmp( old, val ) )
933        {
934            return;
935        }
936    }
937    cf_setpref( name, val );
938
939    /* write prefs to disk */
940    cf_saveprefs( &errstr );
941    if( NULL != errstr )
942    {
943        errmsg( GTK_WINDOW( self ), "%s", errstr );
944        g_free( errstr );
945    }
946
947    /* signal a pref change */
948    class = g_type_class_peek( TR_PREFS_TYPE );
949    g_signal_emit( self, class->changesig, 0, id );
950}
Note: See TracBrowser for help on using the repository browser.