source: trunk/gtk/tr_prefs.c @ 2154

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