source: trunk/gtk/tr_prefs.c @ 1587

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

Set roles for non-dialog windows to help WMs out a bit.

  • Property svn:keywords set to Date Rev Author Id
File size: 27.2 KB
Line 
1/******************************************************************************
2 * $Id: tr_prefs.c 1587 2007-03-24 10:20:00Z 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    gtk_window_set_role( GTK_WINDOW( self ), "tr-prefs" );
328    title = g_strdup_printf( _("%s Preferences"), g_get_application_name() );
329    gtk_window_set_title( GTK_WINDOW( self ), title );
330    g_free( title );
331    gtk_dialog_set_has_separator( GTK_DIALOG( self ), FALSE );
332    gtk_dialog_add_button( GTK_DIALOG( self ), GTK_STOCK_CLOSE,
333                           GTK_RESPONSE_CLOSE );
334    gtk_widget_set_name( GTK_WIDGET( self ), "TransmissionDialog");
335    gtk_dialog_set_default_response( GTK_DIALOG( self ), GTK_RESPONSE_CLOSE );
336    gtk_container_set_border_width( GTK_CONTAINER( self ), 6 );
337    gtk_window_set_resizable( GTK_WINDOW( self ), FALSE );
338
339    rows = countprefs();
340    table = gtk_table_new( rows, 2, FALSE );
341    gtk_table_set_col_spacings( GTK_TABLE( table ), 8 );
342    gtk_table_set_row_spacings( GTK_TABLE( table ), 8 );
343
344    tips = gtk_tooltips_new();
345    g_object_ref( tips );
346    gtk_object_sink( GTK_OBJECT( tips ) );
347    gtk_tooltips_enable( tips );
348    g_signal_connect_swapped( self, "destroy",
349                              G_CALLBACK( g_object_unref ), tips );
350
351    memset( links, 0, sizeof( links ) );
352    makelinks( links );
353    off = 0;
354    for( ii = 0; PREF_MAX_ID > ii; ii++ )
355    {
356        if( PR_SKIP != defs[ii].status )
357        {
358            addwidget( self, ii, GTK_TABLE( table ), off, tips, links );
359            off++;
360        }
361    }
362    g_assert( rows == off );
363    for( ii = 0; ALEN( links ) > ii; ii++ )
364    {
365        g_assert( NULL == links[ii] || NULL != links[ii]->check );
366        if( NULL != links[ii] )
367        {
368            pokelink( links[ii] );
369        }
370    }
371
372    gtk_box_pack_start_defaults( GTK_BOX( GTK_DIALOG( self )->vbox ), table );
373    g_signal_connect( self, "response", G_CALLBACK( gotresp ), NULL );
374    gtk_widget_show_all( table );
375}
376
377static void
378tr_prefs_set_property( GObject * object, guint property_id,
379                      const GValue * value, GParamSpec * pspec)
380{
381    TrPrefs * self = ( TrPrefs * )object;
382
383    if( self->disposed )
384    {
385        return;
386    }
387
388    switch( property_id )
389    {
390        case PROP_PARENT:
391            gtk_window_set_transient_for( GTK_WINDOW( self ),
392                                          g_value_get_object( value ) );
393            break;
394        default:
395            G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
396            break;
397    }
398}
399
400static void
401tr_prefs_get_property( GObject * object, guint property_id,
402                      GValue * value, GParamSpec * pspec )
403{
404    TrPrefs   * self = ( TrPrefs * )object;
405    GtkWindow * trans;
406
407    if( self->disposed )
408    {
409        return;
410    }
411
412    switch( property_id )
413    {
414        case PROP_PARENT:
415            trans = gtk_window_get_transient_for( GTK_WINDOW( self ) );
416            g_value_set_object( value, trans );
417            break;
418        default:
419            G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
420            break;
421    }
422}
423
424static void
425tr_prefs_dispose( GObject * obj )
426{
427    TrPrefs      * self = ( TrPrefs * )obj;
428    GObjectClass * parent;
429
430    if( self->disposed )
431    {
432        return;
433    }
434    self->disposed = TRUE;
435
436    g_object_unref( self->combomodel );
437
438    /* Chain up to the parent class */
439    parent = g_type_class_peek( g_type_parent( TR_PREFS_TYPE ) );
440    parent->dispose( obj );
441}
442
443TrPrefs *
444tr_prefs_new( void )
445{
446    return g_object_new( TR_PREFS_TYPE, NULL );
447}
448
449TrPrefs *
450tr_prefs_new_with_parent( GtkWindow * parent )
451{
452    return g_object_new( TR_PREFS_TYPE, "parent", parent, NULL );
453}
454
455const char *
456tr_prefs_name( int id )
457{
458    g_assert( 0 <= id && PREF_MAX_ID > id  && ALEN( defs ) == PREF_MAX_ID );
459    return defs[id].name;
460}
461
462gboolean
463tr_prefs_get_int( int id, int * val )
464{
465    const char * str;
466    char       * end;
467    int          ret;
468
469    str = tr_prefs_get( id );
470    if( NULL == str || '\0' == *str )
471    {
472        return FALSE;
473    }
474
475    errno = 0;
476    ret = strtol( str, &end, 10 );
477    if( 0 != errno || NULL == end || '\0' != *end )
478    {
479        return FALSE;
480    }
481    *val = ret;
482    return TRUE;
483}
484
485gboolean
486tr_prefs_get_bool( int id, gboolean * val )
487{
488    const char * str;
489
490    str = tr_prefs_get( id );
491    if( NULL == str )
492    {
493        return FALSE;
494    }
495    *val = strbool( str );
496    return TRUE;
497}
498
499int
500tr_prefs_get_int_with_default( int id )
501{
502    int ret;
503
504    g_assert( 0 <= id && ALEN( defs ) > id &&
505              G_TYPE_INT == PTYPE( id ) && ALEN( defs_int ) > id );
506
507    if( tr_prefs_get_int( id, &ret ) )
508    {
509        return ret;
510    }
511    return defs_int[id].def;
512}
513
514gboolean
515tr_prefs_get_bool_with_default( int id )
516{
517    gboolean ret;
518
519    g_assert( 0 <= id && ALEN( defs ) > id &&
520              G_TYPE_BOOLEAN == PTYPE( id ) && ALEN( defs_bool ) > id );
521
522    if( tr_prefs_get_bool( id, &ret ) )
523    {
524        return ret;
525    }
526    return defs_bool[id].def;
527
528}
529
530static void
531gotresp( GtkWidget * widget, int resp SHUTUP, gpointer data SHUTUP )
532{
533    gtk_widget_destroy( widget );
534}
535
536static int
537countprefs( void )
538{
539    int ii, ret;
540
541    g_assert( ALEN( defs ) == PREF_MAX_ID );
542    ret = 0;
543    for( ii = 0; PREF_MAX_ID > ii; ii++ )
544    {
545        if( PR_SKIP != defs[ii].status )
546        {
547            ret++;
548        }
549    }
550
551    return ret;
552}
553
554static void
555makelinks( struct checkctl ** links )
556{
557    int ii;
558
559    g_assert( ALEN( defs ) == PREF_MAX_ID );
560    for( ii = 0; PREF_MAX_ID > ii; ii++ )
561    {
562        if( PR_SKIP == defs[ii].status || G_TYPE_BOOLEAN != PTYPE( ii ) )
563        {
564            continue;
565        }
566        g_assert( ALEN( defs_bool ) > ii );
567        if( 0 <= defs_bool[ii].link )
568        {
569            links[ii] = g_new0( struct checkctl, 1 );
570        }
571    }
572}
573
574static void
575filllinks( int id, GtkWidget * wid1, GtkWidget * wid2,
576           struct checkctl ** links )
577{
578    int ii;
579
580    g_assert( ALEN( defs ) >= ALEN( defs_bool ) );
581    for( ii = 0; ALEN( defs_bool) > ii; ii++ )
582    {
583        if( NULL == links[ii] )
584        {
585            g_assert( PR_SKIP == defs[ii].status ||
586                      G_TYPE_BOOLEAN != PTYPE( ii ) ||
587                      0 > defs_bool[ii].link );
588        }
589        else
590        {
591            g_assert( PR_SKIP != defs[ii].status &&
592                      G_TYPE_BOOLEAN == PTYPE( ii ) &&
593                      0 <= defs_bool[ii].link );
594            if( id == defs_bool[ii].link )
595            {
596                links[ii]->wids[0] = wid1;
597                links[ii]->wids[1] = wid2;
598            }
599        }
600    }
601}
602
603static void
604pokelink( struct checkctl * link )
605{
606    gboolean active;
607
608    active = gtk_toggle_button_get_active( link->check );
609    active = ( link->enables ? active : !active );
610    gtk_widget_set_sensitive( link->wids[0], active );
611    gtk_widget_set_sensitive( link->wids[1], active );
612}
613
614static void
615addwidget( TrPrefs * self, int id, GtkTable * table, int off,
616           GtkTooltips * tips, struct checkctl ** links )
617{
618    GType       type;
619    GtkWidget * add1, * add2;
620
621    g_assert( ALEN( defs ) > id );
622
623    type = PTYPE( id );
624    add1 = NULL;
625    add2 = NULL;
626    if( G_TYPE_BOOLEAN == type )
627    {
628        addwid_bool( self, id, tips, &add1, links );
629    }
630    else if( G_TYPE_INT == type )
631    {
632        addwid_int( self, id, tips, &add1, &add2 );
633    }
634    else if( GTK_TYPE_FILE_CHOOSER == type )
635    {
636        addwid_file( self, id, tips, &add1, &add2 );
637    }
638    else if( GTK_TYPE_COMBO_BOX == type )
639    {
640        addwid_combo( self, id, tips, &add1, &add2 );
641    }
642    else
643    {
644        g_assert_not_reached();
645    }
646
647    g_assert( NULL != add1 );
648    filllinks( id, add1, add2, links );
649    if( NULL == add2 )
650    {
651        gtk_table_attach_defaults( table, add1, 0, 2, off, off + 1 );
652    }
653    else
654    {
655        gtk_table_attach_defaults( table, add1, 0, 1, off, off + 1 );
656        gtk_table_attach_defaults( table, add2, 1, 2, off, off + 1 );
657    }
658    if( PR_DISABLED == defs[id].status )
659    {
660        gtk_widget_set_sensitive( add1, FALSE );
661        if( NULL != add2 )
662        {
663            gtk_widget_set_sensitive( add2, FALSE );
664        }
665    }
666}
667
668/* wrap a widget in an event box with a tooltip */
669static GtkWidget *
670tipbox( GtkWidget * widget, GtkTooltips * tips, const char * tip )
671{
672    GtkWidget * box;
673
674    box = gtk_event_box_new();
675    gtk_container_add( GTK_CONTAINER( box ), widget );
676    gtk_tooltips_set_tip( tips, box, tip, "" );
677
678    return box;
679}
680
681static void
682addwid_bool( TrPrefs * self, int id, GtkTooltips * tips,
683             GtkWidget ** wid1, struct checkctl ** links )
684{
685    GtkWidget  * check;
686    gboolean     active;
687
688    g_assert( ALEN( defs ) > id && G_TYPE_BOOLEAN == PTYPE( id ) );
689    check = gtk_check_button_new_with_mnemonic( gettext( defs[id].label ) );
690    gtk_tooltips_set_tip( tips, check, gettext( defs[id].tip ), "" );
691    if( 0 > defs_bool[id].link )
692    {
693        g_assert( NULL == links[id] );
694    }
695    else
696    {
697        links[id]->check = GTK_TOGGLE_BUTTON( check );
698        links[id]->enables = defs_bool[id].enables;
699        g_object_set_data_full( G_OBJECT( check ), PREF_CHECK_LINK,
700                                links[id], g_free );
701    }
702    active = tr_prefs_get_bool_with_default( id );
703    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check ), active );
704    SETPREFID( check, id );
705    g_signal_connect( check, "clicked", G_CALLBACK( checkclick ), self );
706
707    *wid1 = check;
708}
709
710static void
711checkclick( GtkWidget * widget, gpointer data )
712{
713    TrPrefs         * self;
714    struct checkctl * link;
715    int               id;
716    gboolean          active;
717
718    TR_IS_PREFS( data );
719    self = TR_PREFS( data );
720    link = g_object_get_data( G_OBJECT( widget ), PREF_CHECK_LINK );
721    GETPREFID( widget, id );
722
723    if( NULL != link )
724    {
725        pokelink( link );
726    }
727
728    active = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( widget ) );
729    savepref( self, id, ( active ? "yes" : "no" ) );
730}
731
732static void
733addwid_int( TrPrefs * self, int id, GtkTooltips * tips,
734            GtkWidget ** wid1, GtkWidget ** wid2 )
735{
736    GtkWidget * spin, * label;
737    int         val, * last;
738
739    g_assert( ALEN( defs ) > id && G_TYPE_INT == PTYPE( id ) );
740    spin = gtk_spin_button_new_with_range( defs_int[id].min,
741                                           defs_int[id].max, 1 );
742    label = gtk_label_new_with_mnemonic( gettext( defs[id].label ) );
743    gtk_label_set_mnemonic_widget( GTK_LABEL( label ), spin );
744    gtk_misc_set_alignment( GTK_MISC( label ), 0, .5 );
745    gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin ), TRUE );
746    gtk_tooltips_set_tip( tips, spin, gettext( defs[id].tip ), "" );
747    val = tr_prefs_get_int_with_default( id );
748    gtk_spin_button_set_value( GTK_SPIN_BUTTON( spin ), val );
749    last = g_new( int, 1 );
750    *last = val;
751    g_object_set_data_full( G_OBJECT( spin ), PREF_SPIN_LAST, last, g_free );
752    SETPREFID( spin, id );
753    /* I don't trust that focus-out-event will always work,
754       so save pref on widget destruction too */
755    g_signal_connect( spin, "focus-out-event", G_CALLBACK( spinfocus ), self );
756    g_signal_connect( spin, "destroy", G_CALLBACK( spindie ), self );
757
758    *wid1 = tipbox( label, tips, gettext( defs[id].tip ) );
759    *wid2 = spin;
760}
761
762static gboolean
763spinfocus( GtkWidget * widget, GdkEventFocus *event SHUTUP, gpointer data )
764{
765    TrPrefs * self;
766    int     * last, id, cur;
767    char    * str;
768
769    TR_IS_PREFS( data );
770    self = TR_PREFS( data );
771    last = g_object_get_data( G_OBJECT( widget ), PREF_SPIN_LAST );
772    GETPREFID( widget, id );
773    cur = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( widget ) );
774
775    if( cur != *last )
776    {
777        str = g_strdup_printf( "%i", cur );
778        savepref( self, id, str );
779        g_free( str );
780        *last = cur;
781    }
782
783    /* continue propagating the event */
784    return FALSE;
785}
786
787static void
788spindie( GtkWidget * widget, gpointer data )
789{
790    spinfocus( widget, NULL, data );
791}
792
793static void
794addwid_file( TrPrefs * self, int id, GtkTooltips * tips,
795             GtkWidget ** wid1, GtkWidget ** wid2 )
796{
797    GtkWidget  * file, * label;
798    const char * pref;
799
800    g_assert( ALEN( defs ) > id && GTK_TYPE_FILE_CHOOSER == PTYPE( id ) );
801    file = gtk_file_chooser_button_new( gettext( defs_file[id].title ),
802                                        defs_file[id].act );
803    label = gtk_label_new_with_mnemonic( gettext( defs[id].label ) );
804    gtk_label_set_mnemonic_widget( GTK_LABEL( label ), file );
805    gtk_misc_set_alignment( GTK_MISC( label ), 0, .5 );
806    pref = tr_prefs_get( id );
807    if( NULL == pref )
808    {
809        pref = defs_file[id].getdef();
810    }
811    gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( file ), pref );
812    SETPREFID( file, id );
813    g_signal_connect( file, "selection-changed",
814                      G_CALLBACK( filechosen ), self );
815
816    *wid1 = tipbox( label, tips, gettext( defs[id].tip ) );
817    *wid2 = tipbox( file,  tips, gettext( defs[id].tip ) );
818}
819
820static void
821filechosen( GtkWidget * widget, gpointer data )
822{
823    TrPrefs    * self;
824    const char * dir;
825    int          id;
826
827    TR_IS_PREFS( data );
828    self = TR_PREFS( data );
829    dir = gtk_file_chooser_get_current_folder( GTK_FILE_CHOOSER( widget ) );
830    GETPREFID( widget, id );
831    savepref( self, id, dir );
832}
833
834static GtkTreeModel *
835makecombomodel( void )
836{
837    GtkListStore * list;
838    GtkTreeIter    iter;
839
840    /* create the model used by the two popup menus */
841    list = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_INT );
842    gtk_list_store_append( list, &iter );
843    gtk_list_store_set( list, &iter, 1, 0, 0,
844                        _("Use the torrent file where it is"), -1 );
845    gtk_list_store_append( list, &iter );
846    gtk_list_store_set( list, &iter, 1, TR_TORNEW_SAVE_COPY, 0,
847                        _("Keep a copy of the torrent file"), -1 );
848    gtk_list_store_append( list, &iter );
849    gtk_list_store_set( list, &iter, 1, TR_TORNEW_SAVE_MOVE, 0,
850                        _("Keep a copy and remove the original"), -1 );
851
852    return GTK_TREE_MODEL( list );
853}
854
855static void
856addwid_combo( TrPrefs * self, int id, GtkTooltips * tips,
857              GtkWidget ** wid1, GtkWidget ** wid2 )
858{
859    GtkWidget       * combo, * label;
860    GtkCellRenderer * rend;
861    GtkTreeIter       iter;
862    guint             prefsflag, modelflag;
863
864    g_assert( ALEN( defs ) > id && GTK_TYPE_COMBO_BOX == PTYPE( id ) );
865    combo = gtk_combo_box_new();
866    label = gtk_label_new_with_mnemonic( gettext( defs[id].label ) );
867    gtk_label_set_mnemonic_widget( GTK_LABEL( label ), combo );
868    gtk_misc_set_alignment( GTK_MISC( label ), 0, .5 );
869    gtk_combo_box_set_model( GTK_COMBO_BOX( combo ), self->combomodel );
870    rend = gtk_cell_renderer_text_new();
871    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( combo ), rend, TRUE );
872    gtk_cell_layout_add_attribute( GTK_CELL_LAYOUT( combo ), rend, "text", 0 );
873
874    prefsflag = addactionflag( tr_prefs_get( id ) );
875    if( gtk_tree_model_get_iter_first( self->combomodel, &iter ) )
876    {
877        do
878        {
879            gtk_tree_model_get( self->combomodel, &iter, 1, &modelflag, -1 );
880            if( modelflag == prefsflag)
881            {
882                gtk_combo_box_set_active_iter( GTK_COMBO_BOX( combo ), &iter );
883                break;
884            }
885        }
886        while( gtk_tree_model_iter_next( self->combomodel, &iter ) );
887    }
888    SETPREFID( combo, id );
889    g_signal_connect( combo, "changed", G_CALLBACK( combochosen ), self );
890
891    *wid1 = tipbox( label, tips, gettext( defs[id].tip ) );
892    *wid2 = tipbox( combo, tips, gettext( defs[id].tip ) );
893}
894
895static void
896combochosen( GtkWidget * widget, gpointer data )
897{
898    TrPrefs      * self;
899    GtkTreeIter    iter;
900    GtkTreeModel * model;
901    guint          flags;
902    int            id;
903
904    TR_IS_PREFS( data );
905    self = TR_PREFS( data );
906    if( gtk_combo_box_get_active_iter( GTK_COMBO_BOX( widget ), &iter ) )
907    {
908        model = gtk_combo_box_get_model( GTK_COMBO_BOX( widget ) );
909        gtk_tree_model_get( model, &iter, 1, &flags, -1 );
910        GETPREFID( widget, id );
911        savepref( self, id, addactionname( flags ) );
912    }
913}
914
915static void
916savepref( TrPrefs * self, int id, const char * val )
917{
918    const char   * name, * old;
919    char         * errstr;
920    TrPrefsClass * class;
921
922    name = tr_prefs_name( id );
923    old = cf_getpref( name );
924    if( NULL == old )
925    {
926        if( old == val )
927        {
928            return;
929        }
930    }
931    else
932    {
933        if( 0 == strcmp( old, val ) )
934        {
935            return;
936        }
937    }
938    cf_setpref( name, val );
939
940    /* write prefs to disk */
941    cf_saveprefs( &errstr );
942    if( NULL != errstr )
943    {
944        errmsg( GTK_WINDOW( self ), "%s", errstr );
945        g_free( errstr );
946    }
947
948    /* signal a pref change */
949    class = g_type_class_peek( TR_PREFS_TYPE );
950    g_signal_emit( self, class->changesig, 0, id );
951}
Note: See TracBrowser for help on using the repository browser.