source: trunk/gtk/main.c @ 11831

Last change on this file since 11831 was 11831, checked in by jordan, 11 years ago

(trunk libT) #3980 "segfault when adding many torrents remotely" -- possible fix.

gtk/main.c's onRPCChanged() is called from inside the libtransmission thread, yet it still made GTK+ calls to modify the GTK+ client's tr-core object when a torrent was added. This caused a race condition inside of the GTK+ internals. onRPCChanged() already knows to delegate work back to the GTK+ thread when a torrent is removed via RPC. This commit uses the same kind of mechanism to delegate work back to the GTK+ thread when a torrent is added via RPC.

  • Property svn:keywords set to Date Rev Author Id
File size: 60.0 KB
Line 
1/******************************************************************************
2 * $Id: main.c 11831 2011-02-05 05:03:00Z jordan $
3 *
4 * Copyright (c) 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 <locale.h>
26#include <signal.h>
27#include <string.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <sys/param.h>
31#include <time.h>
32#include <unistd.h>
33
34#include <curl/curl.h>
35
36#include <gtk/gtk.h>
37#include <glib/gi18n.h>
38#include <glib/gstdio.h>
39
40#ifdef HAVE_GCONF2
41 #include <gconf/gconf.h>
42 #include <gconf/gconf-client.h>
43#endif
44
45#include <libtransmission/transmission.h>
46#include <libtransmission/rpcimpl.h>
47#include <libtransmission/utils.h>
48#include <libtransmission/version.h>
49#include <libtransmission/web.h>
50
51#include "actions.h"
52#include "conf.h"
53#include "details.h"
54#include "dialogs.h"
55#include "hig.h"
56#include "makemeta-ui.h"
57#include "msgwin.h"
58#include "notify.h"
59#include "open-dialog.h"
60#include "relocate.h"
61#include "stats.h"
62#include "tr-core.h"
63#include "tr-icon.h"
64#include "tr-prefs.h"
65#include "tr-torrent.h"
66#include "tr-window.h"
67#include "util.h"
68#include "ui.h"
69
70#define MY_CONFIG_NAME "transmission"
71#define MY_READABLE_NAME "transmission-gtk"
72
73#if GTK_CHECK_VERSION( 2, 8, 0 )
74 #define SHOW_LICENSE
75static const char * LICENSE =
76"The OS X client, CLI client, and parts of libtransmission are licensed under the terms of the MIT license.\n\n"
77"The Transmission daemon, GTK+ client, Qt client, Web client, and most of libtransmission are licensed under the terms of the GNU GPL version 2, with two special exceptions:\n\n"
78"1. The MIT-licensed portions of Transmission listed above are exempt from GPLv2 clause 2(b) and may retain their MIT license.\n\n"
79"2. Permission is granted to link the code in this release with the OpenSSL project's 'OpenSSL' library and to distribute the linked executables. Works derived from Transmission may, at their authors' discretion, keep or delete this exception.";
80#endif
81
82struct cbdata
83{
84    gboolean            isIconified;
85    gboolean            isClosing;
86    guint               timer;
87    guint               refresh_actions_tag;
88    gpointer            icon;
89    GtkWindow         * wind;
90    TrCore            * core;
91    GtkWidget         * msgwin;
92    GtkWidget         * prefs;
93    GSList            * error_list;
94    GSList            * duplicates_list;
95    GSList            * details;
96    GtkTreeSelection  * sel;
97    gpointer            quit_dialog;
98};
99
100/***
101****
102****  DETAILS DIALOGS MANAGEMENT
103****
104***/
105
106static void
107gtr_window_present( GtkWindow * window )
108{
109#if GTK_CHECK_VERSION( 2, 8, 0 )
110    gtk_window_present_with_time( window, gtk_get_current_event_time( ) );
111#else
112    gtk_window_present( window );
113#endif
114}
115
116/***
117****
118****  DETAILS DIALOGS MANAGEMENT
119****
120***/
121
122static int
123compare_integers( const void * a, const void * b )
124{
125    return *(int*)a - *(int*)b;
126}
127
128static char*
129get_details_dialog_key( GSList * id_list )
130{
131    int i;
132    int n;
133    int * ids;
134    GSList * l;
135    GString * gstr = g_string_new( NULL );
136
137    n = g_slist_length( id_list );
138    ids = g_new( int, n );
139    i = 0;
140    for( l=id_list; l!=NULL; l=l->next )
141        ids[i++] = GPOINTER_TO_INT( l->data );
142    g_assert( i == n );
143    qsort( ids, n, sizeof(int), compare_integers );
144
145    for( i=0; i<n; ++i )
146        g_string_append_printf( gstr, "%d ", ids[i] );
147
148    g_free( ids );
149    return g_string_free( gstr, FALSE );
150}
151
152struct DetailsDialogHandle
153{
154    char * key;
155    GtkWidget * dialog;
156};
157
158static GSList*
159getSelectedTorrentIds( struct cbdata * data )
160{
161    GList * l;
162    GtkTreeModel * model;
163    GSList * ids = NULL;
164    GList * paths = NULL;
165    GtkTreeSelection * s = data->sel;
166
167    /* build a list of the selected torrents' ids */
168    for( paths=l=gtk_tree_selection_get_selected_rows(s,&model); l; l=l->next ) {
169        GtkTreeIter iter;
170        if( gtk_tree_model_get_iter( model, &iter, l->data ) ) {
171            tr_torrent * tor;
172            gtk_tree_model_get( model, &iter, MC_TORRENT_RAW, &tor, -1 );
173            ids = g_slist_append( ids, GINT_TO_POINTER( tr_torrentId( tor ) ) );
174        }
175    }
176
177    /* cleanup */
178    g_list_foreach( paths, (GFunc)gtk_tree_path_free, NULL );
179    g_list_free( paths );
180    return ids;
181}
182
183static struct DetailsDialogHandle*
184find_details_dialog_from_ids( struct cbdata * cbdata, GSList * ids )
185{
186    GSList * l;
187    struct DetailsDialogHandle * ret = NULL;
188    char * key = get_details_dialog_key( ids );
189
190    for( l=cbdata->details; l!=NULL && ret==NULL; l=l->next ) {
191        struct DetailsDialogHandle * h = l->data;
192        if( !strcmp( h->key, key ) )
193            ret = h;
194    }
195
196    g_free( key );
197    return ret;
198}
199
200static struct DetailsDialogHandle*
201find_details_dialog_from_widget( struct cbdata * cbdata, gpointer w )
202{
203    GSList * l;
204    struct DetailsDialogHandle * ret = NULL;
205
206    for( l=cbdata->details; l!=NULL && ret==NULL; l=l->next ) {
207        struct DetailsDialogHandle * h = l->data;
208        if( h->dialog == w )
209            ret = h;
210    }
211
212    return ret;
213}
214
215static void
216on_details_dialog_closed( gpointer gdata, GObject * dead )
217{
218    struct cbdata * data = gdata;
219    struct DetailsDialogHandle * h = find_details_dialog_from_widget( data, dead );
220
221    if( h != NULL )
222    {
223        data->details = g_slist_remove( data->details, h );
224        g_free( h->key );
225        g_free( h );
226    }
227}
228
229static void
230show_details_dialog_for_selected_torrents( struct cbdata * data )
231{
232    GtkWidget * w;
233    GSList * ids = getSelectedTorrentIds( data );
234    struct DetailsDialogHandle * h = find_details_dialog_from_ids( data, ids );
235
236    if( h != NULL )
237        w = h->dialog;
238    else {
239        h = g_new( struct DetailsDialogHandle, 1 );
240        h->key = get_details_dialog_key( ids );
241        h->dialog = w = gtr_torrent_details_dialog_new( data->wind, data->core );
242        gtr_torrent_details_dialog_set_torrents( w, ids );
243        data->details = g_slist_append( data->details, h );
244        g_object_weak_ref( G_OBJECT( w ), on_details_dialog_closed, data );
245        gtk_widget_show( w );
246    }
247    gtr_window_present( GTK_WINDOW( w ) );
248    g_slist_free( ids );
249}
250
251/****
252*****
253*****  ON SELECTION CHANGED
254*****
255****/
256
257struct counts_data
258{
259    int total_count;
260    int active_count;
261    int inactive_count;
262};
263
264static void
265get_selected_torrent_counts_foreach( GtkTreeModel * model, GtkTreePath * path UNUSED,
266                                     GtkTreeIter * iter, gpointer user_data )
267{
268    int activity = 0;
269    struct counts_data * counts = user_data;
270
271    ++counts->total_count;
272
273    gtk_tree_model_get( model, iter, MC_ACTIVITY, &activity, -1 );
274
275    if( activity == TR_STATUS_STOPPED )
276        ++counts->inactive_count;
277    else
278        ++counts->active_count;
279}
280
281static void
282get_selected_torrent_counts( struct cbdata * data, struct counts_data * counts )
283{
284    counts->active_count = 0;
285    counts->inactive_count = 0;
286    counts->total_count = 0;
287
288    gtk_tree_selection_selected_foreach( data->sel, get_selected_torrent_counts_foreach, counts );
289}
290
291static void
292count_updatable_foreach( GtkTreeModel * model, GtkTreePath * path UNUSED,
293                         GtkTreeIter * iter, gpointer accumulated_status )
294{
295    tr_torrent * tor;
296    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
297    *(int*)accumulated_status |= tr_torrentCanManualUpdate( tor );
298}
299
300static gboolean
301refresh_actions( gpointer gdata )
302{
303    int canUpdate;
304    struct counts_data sel_counts;
305    struct cbdata * data = gdata;
306    const size_t total = tr_core_get_torrent_count( data->core );
307    const size_t active = tr_core_get_active_torrent_count( data->core );
308    const int torrent_count = gtk_tree_model_iter_n_children( tr_core_model( data->core ), NULL );
309
310    gtr_action_set_sensitive( "select-all", torrent_count != 0 );
311    gtr_action_set_sensitive( "deselect-all", torrent_count != 0 );
312    gtr_action_set_sensitive( "pause-all-torrents", active != 0 );
313    gtr_action_set_sensitive( "start-all-torrents", active != total );
314
315    get_selected_torrent_counts( data, &sel_counts );
316    gtr_action_set_sensitive( "pause-torrent", sel_counts.active_count != 0 );
317    gtr_action_set_sensitive( "start-torrent", sel_counts.inactive_count != 0 );
318    gtr_action_set_sensitive( "remove-torrent", sel_counts.total_count != 0 );
319    gtr_action_set_sensitive( "delete-torrent", sel_counts.total_count != 0 );
320    gtr_action_set_sensitive( "verify-torrent", sel_counts.total_count != 0 );
321    gtr_action_set_sensitive( "relocate-torrent", sel_counts.total_count != 0 );
322    gtr_action_set_sensitive( "show-torrent-properties", sel_counts.total_count != 0 );
323    gtr_action_set_sensitive( "open-torrent-folder", sel_counts.total_count == 1 );
324    gtr_action_set_sensitive( "copy-magnet-link-to-clipboard", sel_counts.total_count == 1 );
325
326    canUpdate = 0;
327    gtk_tree_selection_selected_foreach( data->sel, count_updatable_foreach, &canUpdate );
328    gtr_action_set_sensitive( "update-tracker", canUpdate != 0 );
329
330    data->refresh_actions_tag = 0;
331    return FALSE;
332}
333
334static void
335on_selection_changed( GtkTreeSelection * s UNUSED, gpointer gdata )
336{
337    struct cbdata * data = gdata;
338
339    if( data->refresh_actions_tag == 0 )
340        data->refresh_actions_tag = gtr_idle_add( refresh_actions, data );
341}
342
343/***
344****
345****
346***/
347
348static void appsetup( TrWindow       * wind,
349                      GSList         * args,
350                      struct cbdata  * cbdata,
351                      gboolean         paused,
352                      gboolean         minimized );
353
354static void winsetup( struct cbdata * cbdata, TrWindow * wind );
355
356static void wannaquit( gpointer vdata );
357
358static void on_core_error( TrCore *, guint, const char *, struct cbdata * );
359
360static void on_add_torrent( TrCore *, tr_ctor *, gpointer );
361
362static void on_prefs_changed( TrCore * core, const char * key, gpointer );
363
364static gboolean updatemodel( gpointer gdata );
365
366/***
367****
368***/
369
370static void
371registerMagnetLinkHandler( void )
372{
373#ifdef HAVE_GCONF2
374    GError * err;
375    GConfValue * value;
376    GConfClient * client = gconf_client_get_default( );
377    const char * key = "/desktop/gnome/url-handlers/magnet/command";
378
379    /* if there's already a manget handler registered, don't do anything */
380    value = gconf_client_get( client, key, NULL );
381    if( value != NULL )
382    {
383        gconf_value_free( value );
384        return;
385    }
386
387    err = NULL;
388    if( !gconf_client_set_string( client, key, "transmission '%s'", &err ) )
389    {
390        tr_inf( "Unable to register Transmission as default magnet link handler: \"%s\"", err->message );
391        g_clear_error( &err );
392    }
393    else
394    {
395        gconf_client_set_bool( client, "/desktop/gnome/url-handlers/magnet/needs_terminal", FALSE, NULL );
396        gconf_client_set_bool( client, "/desktop/gnome/url-handlers/magnet/enabled", TRUE, NULL );
397        tr_inf( "Transmission registered as default magnet link handler" );
398    }
399
400    g_object_unref( G_OBJECT( client ) );
401#endif
402}
403
404static void
405onMainWindowSizeAllocated( GtkWidget      * gtk_window,
406                           GtkAllocation  * alloc UNUSED,
407                           gpointer         gdata UNUSED )
408{
409    GdkWindow * gdk_window = gtr_widget_get_window( gtk_window );
410    const gboolean isMaximized = ( gdk_window != NULL )
411                              && ( gdk_window_get_state( gdk_window ) & GDK_WINDOW_STATE_MAXIMIZED );
412
413    gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_IS_MAXIMIZED, isMaximized );
414
415    if( !isMaximized )
416    {
417        int x, y, w, h;
418        gtk_window_get_position( GTK_WINDOW( gtk_window ), &x, &y );
419        gtk_window_get_size( GTK_WINDOW( gtk_window ), &w, &h );
420        gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_X, x );
421        gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_Y, y );
422        gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_WIDTH, w );
423        gtr_pref_int_set( PREF_KEY_MAIN_WINDOW_HEIGHT, h );
424    }
425}
426
427static sig_atomic_t global_sigcount = 0;
428static struct cbdata * sighandler_cbdata = NULL;
429
430static void
431signal_handler( int sig )
432{
433    if( ++global_sigcount > 1 )
434    {
435        signal( sig, SIG_DFL );
436        raise( sig );
437    }
438    else switch( sig )
439    {
440        case SIGINT:
441        case SIGTERM:
442            g_message( _( "Got signal %d; trying to shut down cleanly. Do it again if it gets stuck." ), sig );
443            gtr_actions_handler( "quit", sighandler_cbdata );
444            break;
445
446        default:
447            g_message( "unhandled signal" );
448            break;
449    }
450}
451
452struct torrent_idle_data
453{
454    TrCore * core;
455    int id;
456};
457
458static gboolean
459rpc_torrent_remove_idle( gpointer gdata )
460{
461    struct torrent_idle_data * data = gdata;
462
463    tr_core_remove_torrent_from_id( data->core, data->id, FALSE );
464
465    g_free( data );
466    return FALSE; /* tell g_idle not to call this func twice */
467}
468
469static gboolean
470rpc_torrent_add_idle( gpointer gdata )
471{
472    tr_torrent * tor;
473    struct torrent_idle_data * data = gdata;
474
475    if(( tor = tr_torrentFindFromId( tr_core_session( data->core ), data->id )))
476        tr_core_add_torrent( data->core, tr_torrent_new_preexisting( tor ), TRUE );
477
478    g_free( data );
479    return FALSE; /* tell g_idle not to call this func twice */
480}
481
482
483static void
484setupsighandlers( void )
485{
486    signal( SIGINT, signal_handler );
487    signal( SIGKILL, signal_handler );
488}
489
490static tr_rpc_callback_status
491onRPCChanged( tr_session            * session,
492              tr_rpc_callback_type    type,
493              struct tr_torrent     * tor,
494              void                  * gdata )
495{
496    tr_rpc_callback_status status = TR_RPC_OK;
497    struct cbdata * cbdata = gdata;
498    gdk_threads_enter( );
499
500    switch( type )
501    {
502        case TR_RPC_SESSION_CLOSE:
503            gtr_action_activate( "quit" );
504            break;
505
506        case TR_RPC_TORRENT_ADDED: {
507            struct torrent_idle_data * data = g_new0( struct torrent_idle_data, 1 );
508            data->id = tr_torrentId( tor );
509            data->core = cbdata->core;
510            gtr_idle_add( rpc_torrent_add_idle, data );
511            break;
512        }
513
514        case TR_RPC_TORRENT_REMOVING: {
515            struct torrent_idle_data * data = g_new0( struct torrent_idle_data, 1 );
516            data->id = tr_torrentId( tor );
517            data->core = cbdata->core;
518            gtr_idle_add( rpc_torrent_remove_idle, data );
519            status = TR_RPC_NOREMOVE;
520            break;
521        }
522
523        case TR_RPC_SESSION_CHANGED: {
524            int i;
525            tr_benc tmp;
526            tr_benc * newval;
527            tr_benc * oldvals = gtr_pref_get_all( );
528            const char * key;
529            GSList * l;
530            GSList * changed_keys = NULL;
531            tr_bencInitDict( &tmp, 100 );
532            tr_sessionGetSettings( session, &tmp );
533            for( i=0; tr_bencDictChild( &tmp, i, &key, &newval ); ++i )
534            {
535                tr_bool changed;
536                tr_benc * oldval = tr_bencDictFind( oldvals, key );
537                if( !oldval )
538                    changed = TRUE;
539                else {
540                    char * a = tr_bencToStr( oldval, TR_FMT_BENC, NULL );
541                    char * b = tr_bencToStr( newval, TR_FMT_BENC, NULL );
542                    changed = strcmp( a, b ) != 0;
543                    tr_free( b );
544                    tr_free( a );
545                }
546
547                if( changed )
548                    changed_keys = g_slist_append( changed_keys, (gpointer)key );
549            }
550            tr_sessionGetSettings( session, oldvals );
551
552            for( l=changed_keys; l!=NULL; l=l->next )
553                on_prefs_changed( cbdata->core, key, cbdata );
554
555            g_slist_free( changed_keys );
556            tr_bencFree( &tmp );
557            break;
558        }
559
560        case TR_RPC_TORRENT_CHANGED:
561        case TR_RPC_TORRENT_MOVED:
562        case TR_RPC_TORRENT_STARTED:
563        case TR_RPC_TORRENT_STOPPED:
564            /* nothing interesting to do here */
565            break;
566    }
567
568    gdk_threads_leave( );
569    return status;
570}
571
572static GSList *
573checkfilenames( int argc, char **argv )
574{
575    int i;
576    GSList * ret = NULL;
577    char * pwd = g_get_current_dir( );
578
579    for( i=0; i<argc; ++i )
580    {
581        if( gtr_is_supported_url( argv[i] ) || gtr_is_magnet_link( argv[i] ) )
582        {
583            ret = g_slist_prepend( ret, g_strdup( argv[i] ) );
584        }
585        else /* local file */
586        {
587            char * filename = g_path_is_absolute( argv[i] )
588                            ? g_strdup ( argv[i] )
589                            : g_build_filename( pwd, argv[i], NULL );
590
591            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
592                ret = g_slist_prepend( ret, filename );
593            else {
594                if( gtr_is_hex_hashcode( argv[i] ) )
595                    ret = g_slist_prepend( ret, g_strdup_printf( "magnet:?xt=urn:btih:%s", argv[i] ) );
596                g_free( filename );
597            }
598        }
599    }
600
601    g_free( pwd );
602    return g_slist_reverse( ret );
603}
604
605
606#ifdef HAVE_GCONF2
607static void
608applyDesktopProxySettings( CURL * easy, GConfClient * client, const char * host_key, const char * port_key )
609{
610    int port;
611    GConfValue * value;
612    static gboolean env_set;
613    static gboolean env_checked = FALSE;
614
615    /* Both libcurl and GNOME have hooks for proxy support.
616     * If someone has set the http_proxy environment variable,
617     * don't apply the GNOME settings here. That way libcurl can override GNOME. */
618    if( !env_checked ) {
619        const char * str = g_getenv( "http_proxy" );
620        env_set = str && *str;
621        env_checked = TRUE;
622    }
623    if( env_set )
624        return;
625
626    if(( value = gconf_client_get( client, host_key, NULL )))
627    {
628        const char * url = gconf_value_get_string( value );
629
630        if( url && *url )
631        {
632            char * scheme = NULL;
633            GConfValue * port_value;
634
635            if( !tr_urlParse( url, strlen( url ), &scheme, NULL, NULL, NULL ) )
636            {
637                if( !gtr_strcmp0( scheme, "socks4" ) )
638                    curl_easy_setopt( easy, CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS4 );
639                else if( !gtr_strcmp0( scheme, "socks5" ) )
640                    curl_easy_setopt( easy, CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS5 );
641                else if( !gtr_strcmp0( scheme, "http" ) )
642                    curl_easy_setopt( easy, CURLOPT_PROXYTYPE, (long)CURLPROXY_HTTP );
643            }
644
645            curl_easy_setopt( easy, CURLOPT_PROXY, url );
646
647            if( port_key != NULL )
648            {
649                if(( port_value = gconf_client_get( client, port_key, NULL )))
650                {
651                    if(( port = gconf_value_get_int( value )))
652                        curl_easy_setopt( easy, CURLOPT_PROXYPORT, (long)port );
653
654                    gconf_value_free( port_value );
655                }
656            }
657
658            tr_free( scheme );
659        }
660
661        gconf_value_free( value );
662    }
663}
664#endif
665
666static void
667curlConfigFunc( tr_session * session UNUSED, void * vcurl UNUSED, const char * destination )
668{
669#ifdef HAVE_GCONF2
670    GConfValue * value;
671    CURL * easy = vcurl;
672    gboolean use_http_proxy = TRUE;
673    GConfClient * client = gconf_client_get_default( );
674
675    /* get GNOME's proxy mode */
676    if(( value = gconf_client_get( client, "/system/proxy/mode", NULL )))
677    {
678        const char * mode = gconf_value_get_string( value );
679
680        if( !gtr_strcmp0( mode, "auto" ) )
681        {
682            applyDesktopProxySettings( easy, client, "/system/proxy/autoconfig_url", NULL );
683            use_http_proxy = FALSE;
684        }
685        else if( !gtr_strcmp0( mode, "manual" ))
686        {
687            char * scheme = NULL;
688            if( !tr_urlParse( destination, strlen( destination ), &scheme, NULL, NULL, NULL ) )
689            {
690                if( !gtr_strcmp0( scheme, "ftp" ) )
691                {
692                    applyDesktopProxySettings( easy, client, "/system/proxy/ftp_host", "/system/proxy/ftp_port" );
693                    use_http_proxy = FALSE;
694                }
695                else if( !gtr_strcmp0( scheme, "https" ) )
696                {
697                    applyDesktopProxySettings( easy, client, "/system/proxy/secure_host", "/system/proxy/secure_port" );
698                    use_http_proxy = FALSE;
699                }
700            }
701            tr_free( scheme );
702        }
703
704        gconf_value_free( value );
705    }
706
707    /* if this the proxy hasn't been handled yet and "use_http_proxy" is disabled, then don't use a proxy */
708    if( use_http_proxy ) {
709        if(( value = gconf_client_get( client, "/system/http_proxy/use_http_proxy", NULL ))) {
710            use_http_proxy = gconf_value_get_bool( value ) != 0;
711            gconf_value_free( value );
712        }
713    }
714
715    if( use_http_proxy )
716    {
717        gboolean auth = FALSE;
718        applyDesktopProxySettings( easy, client, "/system/http_proxy/host", "/system/http_proxy/port" );
719
720        if(( value = gconf_client_get( client, "/system/http_proxy/use_authentication", NULL )))
721        {
722            auth = gconf_value_get_bool( value );
723            gconf_value_free( value );
724        }
725
726        if( auth )
727        {
728            GConfValue * user_value = gconf_client_get( client, "/system/http_proxy/authentication_user", NULL );
729            const char * user = ( user_value != NULL ) ? gconf_value_get_string( user_value ) : NULL;
730            GConfValue * pass_value = gconf_client_get( client, "/system/http_proxy/authentication_password", NULL );
731            const char * pass = ( pass_value != NULL ) ? gconf_value_get_string( pass_value ) : NULL;
732
733            if( ( user != NULL ) && ( pass != NULL ) )
734            {
735                char * userpass = g_strdup_printf( "%s:%s", user, pass );
736                curl_easy_setopt( easy, CURLOPT_PROXYUSERPWD, userpass );
737                g_free( userpass );
738            }
739
740            if( pass_value ) gconf_value_free( pass_value );
741            if( user_value ) gconf_value_free( user_value );
742        }
743    }
744
745    g_object_unref( G_OBJECT( client ) );
746#endif
747}
748
749int
750main( int argc, char ** argv )
751{
752    char * err = NULL;
753    GSList * argfiles;
754    GError * gerr;
755    gboolean didinit = FALSE;
756    gboolean didlock = FALSE;
757    gboolean showversion = FALSE;
758    gboolean startpaused = FALSE;
759    gboolean startminimized = FALSE;
760    const char * domain = MY_READABLE_NAME;
761    char * configDir = NULL;
762    gtr_lockfile_state_t tr_state;
763
764    GOptionEntry entries[] = {
765        { "paused",     'p', 0, G_OPTION_ARG_NONE,
766          &startpaused, _( "Start with all torrents paused" ), NULL },
767        { "version",    '\0', 0, G_OPTION_ARG_NONE,
768          &showversion, _( "Show version number and exit" ), NULL },
769#ifdef STATUS_ICON_SUPPORTED
770        { "minimized",  'm', 0, G_OPTION_ARG_NONE,
771          &startminimized,
772          _( "Start minimized in notification area" ), NULL },
773#endif
774        { "config-dir", 'g', 0, G_OPTION_ARG_FILENAME, &configDir,
775          _( "Where to look for configuration files" ), NULL },
776        { NULL, 0,   0, 0, NULL, NULL, NULL }
777    };
778
779    /* bind the gettext domain */
780    setlocale( LC_ALL, "" );
781    bindtextdomain( domain, TRANSMISSIONLOCALEDIR );
782    bind_textdomain_codeset( domain, "UTF-8" );
783    textdomain( domain );
784    g_set_application_name( _( "Transmission" ) );
785    tr_formatter_mem_init( mem_K, _(mem_K_str), _(mem_M_str), _(mem_G_str), _(mem_T_str) );
786    tr_formatter_size_init( disk_K, _(disk_K_str), _(disk_M_str), _(disk_G_str), _(disk_T_str) );
787    tr_formatter_speed_init( speed_K, _(speed_K_str), _(speed_M_str), _(speed_G_str), _(speed_T_str) );
788
789    /* initialize gtk */
790    if( !g_thread_supported( ) )
791        g_thread_init( NULL );
792
793    gerr = NULL;
794    if( !gtk_init_with_args( &argc, &argv, (char*)_( "[torrent files or urls]" ), entries,
795                             (char*)domain, &gerr ) )
796    {
797        fprintf( stderr, "%s\n", gerr->message );
798        g_clear_error( &gerr );
799        return 0;
800    }
801
802    if( showversion )
803    {
804        fprintf( stderr, "%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING );
805        return 0;
806    }
807
808    if( configDir == NULL )
809        configDir = (char*) tr_getDefaultConfigDir( MY_CONFIG_NAME );
810
811    gtr_notify_init( );
812    didinit = cf_init( configDir, NULL ); /* must come before actions_init */
813
814    setupsighandlers( ); /* set up handlers for fatal signals */
815
816    didlock = cf_lock( &tr_state, &err );
817    argfiles = checkfilenames( argc - 1, argv + 1 );
818
819    if( !didlock && argfiles )
820    {
821        /* We have torrents to add but there's another copy of Transmsision
822         * running... chances are we've been invoked from a browser, etc.
823         * So send the files over to the "real" copy of Transmission, and
824         * if that goes well, then our work is done. */
825        GSList * l;
826        gboolean delegated = FALSE;
827        const gboolean trash_originals = gtr_pref_flag_get( TR_PREFS_KEY_TRASH_ORIGINAL );
828
829        for( l=argfiles; l!=NULL; l=l->next )
830        {
831            const char * filename = l->data;
832            const gboolean added = gtr_dbus_add_torrent( filename );
833
834            if( added && trash_originals )
835                gtr_file_trash_or_remove( filename );
836
837            delegated |= added;
838        }
839
840        if( delegated ) {
841            g_slist_foreach( argfiles, (GFunc)g_free, NULL );
842            g_slist_free( argfiles );
843            argfiles = NULL;
844
845            if( err ) {
846                g_free( err );
847                err = NULL;
848            }
849        }
850    }
851    else if( ( !didlock ) && ( tr_state == GTR_LOCKFILE_ELOCK ) )
852    {
853        /* There's already another copy of Transmission running,
854         * so tell it to present its window to the user */
855        err = NULL;
856        if( !gtr_dbus_present_window( ) )
857            err = g_strdup( _( "Transmission is already running, but is not responding. To start a new session, you must first close the existing Transmission process." ) );
858    }
859
860    if( didlock && ( didinit || cf_init( configDir, &err ) ) )
861    {
862        /* No other copy of Transmission running...
863         * so we're going to be the primary. */
864
865        const char * str;
866        GtkWindow * win;
867        GtkUIManager * myUIManager;
868        tr_session * session;
869        struct cbdata * cbdata = g_new0( struct cbdata, 1 );
870
871        sighandler_cbdata = cbdata;
872
873        /* ensure the directories are created */
874        if(( str = gtr_pref_string_get( TR_PREFS_KEY_DOWNLOAD_DIR )))
875            gtr_mkdir_with_parents( str, 0777 );
876        if(( str = gtr_pref_string_get( TR_PREFS_KEY_INCOMPLETE_DIR )))
877            gtr_mkdir_with_parents( str, 0777 );
878
879        /* initialize the libtransmission session */
880        session = tr_sessionInit( "gtk", configDir, TRUE, gtr_pref_get_all( ) );
881        tr_sessionSetWebConfigFunc( session, curlConfigFunc );
882
883        gtr_pref_flag_set( TR_PREFS_KEY_ALT_SPEED_ENABLED, tr_sessionUsesAltSpeed( session ) );
884        gtr_pref_int_set( TR_PREFS_KEY_PEER_PORT, tr_sessionGetPeerPort( session ) );
885        cbdata->core = tr_core_new( session );
886
887        /* init the ui manager */
888        myUIManager = gtk_ui_manager_new ( );
889        gtr_actions_init ( myUIManager, cbdata );
890        gtk_ui_manager_add_ui_from_string ( myUIManager, fallback_ui_file, -1, NULL );
891        gtk_ui_manager_ensure_update ( myUIManager );
892        gtk_window_set_default_icon_name ( MY_CONFIG_NAME );
893
894        /* create main window now to be a parent to any error dialogs */
895        win = GTK_WINDOW( gtr_window_new( myUIManager, cbdata->core ) );
896        g_signal_connect( win, "size-allocate", G_CALLBACK( onMainWindowSizeAllocated ), cbdata );
897
898        appsetup( win, argfiles, cbdata, startpaused, startminimized );
899        tr_sessionSetRPCCallback( session, onRPCChanged, cbdata );
900
901        /* on startup, check & see if it's time to update the blocklist */
902        if( gtr_pref_flag_get( TR_PREFS_KEY_BLOCKLIST_ENABLED ) ) {
903            if( gtr_pref_flag_get( PREF_KEY_BLOCKLIST_UPDATES_ENABLED ) ) {
904                const int64_t last_time = gtr_pref_int_get( "blocklist-date" );
905                const int SECONDS_IN_A_WEEK = 7 * 24 * 60 * 60;
906                const time_t now = time( NULL );
907                if( last_time + SECONDS_IN_A_WEEK < now )
908                    tr_core_blocklist_update( cbdata->core );
909            }
910        }
911
912        /* if there's no magnet link handler registered, register us */
913        registerMagnetLinkHandler( );
914
915        gtk_main( );
916    }
917    else if( err )
918    {
919        const char * primary_text = _( "Transmission cannot be started." );
920        GtkWidget * w = gtk_message_dialog_new( NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, primary_text, NULL );
921        gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), "%s", err );
922        g_signal_connect( w, "response", G_CALLBACK(gtk_main_quit), NULL );
923        gtk_widget_show( w );
924        g_free( err );
925        gtk_main( );
926    }
927
928    return 0;
929}
930
931static void
932on_core_busy( TrCore * core UNUSED, gboolean busy, struct cbdata * c )
933{
934    gtr_window_set_busy( c->wind, busy );
935}
936
937static void
938appsetup( TrWindow      * wind,
939          GSList        * torrentFiles,
940          struct cbdata * cbdata,
941          gboolean        forcepause,
942          gboolean        isIconified )
943{
944    const gboolean doStart = gtr_pref_flag_get( TR_PREFS_KEY_START ) && !forcepause;
945    const gboolean doPrompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
946    const gboolean doNotify = TRUE;
947
948    cbdata->isIconified  = isIconified;
949
950    if( isIconified )
951        gtr_pref_flag_set( PREF_KEY_SHOW_TRAY_ICON, TRUE );
952
953    gtr_actions_set_core( cbdata->core );
954
955    /* set up core handlers */
956    g_signal_connect( cbdata->core, "busy", G_CALLBACK( on_core_busy ), cbdata );
957    g_signal_connect( cbdata->core, "add-error", G_CALLBACK( on_core_error ), cbdata );
958    g_signal_connect( cbdata->core, "add-prompt", G_CALLBACK( on_add_torrent ), cbdata );
959    g_signal_connect( cbdata->core, "prefs-changed", G_CALLBACK( on_prefs_changed ), cbdata );
960
961    /* add torrents from command-line and saved state */
962    tr_core_load( cbdata->core, forcepause );
963    tr_core_add_list( cbdata->core, torrentFiles, doStart, doPrompt, doNotify );
964    torrentFiles = NULL;
965    tr_core_torrents_added( cbdata->core );
966
967    /* set up main window */
968    winsetup( cbdata, wind );
969
970    /* set up the icon */
971    on_prefs_changed( cbdata->core, PREF_KEY_SHOW_TRAY_ICON, cbdata );
972
973    /* start model update timer */
974    cbdata->timer = gtr_timeout_add_seconds( MAIN_WINDOW_REFRESH_INTERVAL_SECONDS, updatemodel, cbdata );
975    updatemodel( cbdata );
976
977    /* either show the window or iconify it */
978    if( !isIconified )
979        gtk_widget_show( GTK_WIDGET( wind ) );
980    else
981    {
982        gtk_window_set_skip_taskbar_hint( cbdata->wind,
983                                          cbdata->icon != NULL );
984        cbdata->isIconified = FALSE; // ensure that the next toggle iconifies
985        gtr_action_set_toggled( "toggle-main-window", FALSE );
986    }
987
988    if( !gtr_pref_flag_get( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT ) )
989    {
990        GtkWidget * w = gtk_message_dialog_new( GTK_WINDOW( wind ),
991                                                GTK_DIALOG_DESTROY_WITH_PARENT,
992                                                GTK_MESSAGE_INFO,
993                                                GTK_BUTTONS_NONE,
994                                                "%s",
995             _( "Transmission is a file-sharing program. When you run a torrent, its data will be made available to others by means of upload. You and you alone are fully responsible for exercising proper judgement and abiding by your local laws." ) );
996        gtk_dialog_add_button( GTK_DIALOG( w ), GTK_STOCK_QUIT, GTK_RESPONSE_REJECT );
997        gtk_dialog_add_button( GTK_DIALOG( w ), _( "I _Accept" ), GTK_RESPONSE_ACCEPT );
998        gtk_dialog_set_default_response( GTK_DIALOG( w ), GTK_RESPONSE_ACCEPT );
999        switch( gtk_dialog_run( GTK_DIALOG( w ) ) ) {
1000            case GTK_RESPONSE_ACCEPT:
1001                /* only show it once */
1002                gtr_pref_flag_set( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT, TRUE );
1003                gtk_widget_destroy( w );
1004                break;
1005            default:
1006                exit( 0 );
1007        }
1008    }
1009}
1010
1011static void
1012toggleMainWindow( struct cbdata * cbdata )
1013{
1014    GtkWindow * window = cbdata->wind;
1015    const int   doShow = cbdata->isIconified;
1016    static int  x = 0;
1017    static int  y = 0;
1018
1019    if( doShow )
1020    {
1021        cbdata->isIconified = 0;
1022        gtk_window_set_skip_taskbar_hint( window, FALSE );
1023        gtk_window_move( window, x, y );
1024        gtr_widget_set_visible( GTK_WIDGET( window ), TRUE );
1025        gtr_window_present( window );
1026    }
1027    else
1028    {
1029        gtk_window_get_position( window, &x, &y );
1030        gtk_window_set_skip_taskbar_hint( window, TRUE );
1031        gtr_widget_set_visible( GTK_WIDGET( window ), FALSE );
1032        cbdata->isIconified = 1;
1033    }
1034}
1035
1036static gboolean
1037shouldConfirmBeforeExiting( struct cbdata * data )
1038{
1039    return gtr_pref_flag_get( PREF_KEY_ASKQUIT )
1040        && tr_core_get_active_torrent_count( data->core );
1041}
1042
1043static void
1044maybeaskquit( struct cbdata * cbdata )
1045{
1046    if( !shouldConfirmBeforeExiting( cbdata ) )
1047        wannaquit( cbdata );
1048    else {
1049        if( cbdata->quit_dialog == NULL ) {
1050            cbdata->quit_dialog = gtr_confirm_quit( cbdata->wind, cbdata->core, wannaquit, cbdata );
1051            g_object_add_weak_pointer( G_OBJECT( cbdata->quit_dialog ), &cbdata->quit_dialog );
1052        }
1053        gtr_window_present( GTK_WINDOW( cbdata->quit_dialog ) );
1054    }
1055}
1056
1057static gboolean
1058winclose( GtkWidget * w    UNUSED,
1059          GdkEvent * event UNUSED,
1060          gpointer         gdata )
1061{
1062    struct cbdata * cbdata = gdata;
1063
1064    if( cbdata->icon != NULL )
1065        gtr_action_activate ( "toggle-main-window" );
1066    else
1067        maybeaskquit( cbdata );
1068
1069    return TRUE; /* don't propagate event further */
1070}
1071
1072static void
1073rowChangedCB( GtkTreeModel  * model UNUSED,
1074              GtkTreePath   * path,
1075              GtkTreeIter   * iter  UNUSED,
1076              gpointer        gdata )
1077{
1078    struct cbdata * data = gdata;
1079    if( gtk_tree_selection_path_is_selected ( data->sel, path ) )
1080        refresh_actions( gdata );
1081}
1082
1083static void
1084on_drag_data_received( GtkWidget         * widget          UNUSED,
1085                       GdkDragContext    * drag_context,
1086                       gint                x               UNUSED,
1087                       gint                y               UNUSED,
1088                       GtkSelectionData  * selection_data,
1089                       guint               info            UNUSED,
1090                       guint               time_,
1091                       gpointer            gdata )
1092{
1093    int i;
1094    gboolean success = FALSE;
1095    GSList * filenames = NULL;
1096    struct cbdata * data = gdata;
1097    char ** uris = gtk_selection_data_get_uris( selection_data );
1098
1099    /* try to add the filename URIs... */
1100    for( i=0; uris && uris[i]; ++i )
1101    {
1102        const char * uri = uris[i];
1103        char * filename = g_filename_from_uri( uri, NULL, NULL );
1104
1105        if( filename && g_file_test( filename, G_FILE_TEST_EXISTS ) )
1106        {
1107            filenames = g_slist_append( filenames, g_strdup( filename ) );
1108            success = TRUE;
1109        }
1110        else if( tr_urlIsValid( uri, -1 ) || gtr_is_magnet_link( uri ) )
1111        {
1112            tr_core_add_from_url( data->core, uri );
1113            success = TRUE;
1114        }
1115    }
1116
1117    if( filenames )
1118        tr_core_add_list_defaults( data->core, g_slist_reverse( filenames ), TRUE );
1119
1120    tr_core_torrents_added( data->core );
1121    gtk_drag_finish( drag_context, success, FALSE, time_ );
1122
1123    /* cleanup */
1124    g_strfreev( uris );
1125}
1126
1127static void
1128winsetup( struct cbdata * cbdata, TrWindow * wind )
1129{
1130    GtkWidget * w;
1131    GtkTreeModel * model;
1132    GtkTreeSelection * sel;
1133
1134    g_assert( NULL == cbdata->wind );
1135    cbdata->wind = GTK_WINDOW( wind );
1136    cbdata->sel = sel = GTK_TREE_SELECTION( gtr_window_get_selection( cbdata->wind ) );
1137
1138    g_signal_connect( sel, "changed", G_CALLBACK( on_selection_changed ), cbdata );
1139    on_selection_changed( sel, cbdata );
1140    model = tr_core_model( cbdata->core );
1141    g_signal_connect( model, "row-changed", G_CALLBACK( rowChangedCB ), cbdata );
1142    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
1143    refresh_actions( cbdata );
1144
1145    /* register to handle URIs that get dragged onto our main window */
1146    w = GTK_WIDGET( wind );
1147    gtk_drag_dest_set( w, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY );
1148    gtk_drag_dest_add_uri_targets( w );
1149    g_signal_connect( w, "drag-data-received", G_CALLBACK(on_drag_data_received), cbdata );
1150}
1151
1152static gboolean
1153onSessionClosed( gpointer gdata )
1154{
1155    struct cbdata * cbdata = gdata;
1156
1157    /* shutdown the gui */
1158    while( cbdata->details != NULL ) {
1159        struct DetailsDialogHandle * h = cbdata->details->data;
1160        gtk_widget_destroy( h->dialog );
1161    }
1162
1163    if( cbdata->prefs )
1164        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
1165    if( cbdata->wind )
1166        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
1167    g_object_unref( cbdata->core );
1168    if( cbdata->icon )
1169        g_object_unref( cbdata->icon );
1170    g_slist_foreach( cbdata->error_list, (GFunc)g_free, NULL );
1171    g_slist_free( cbdata->error_list );
1172    g_slist_foreach( cbdata->duplicates_list, (GFunc)g_free, NULL );
1173    g_slist_free( cbdata->duplicates_list );
1174    g_free( cbdata );
1175
1176    gtk_main_quit( );
1177
1178    return FALSE;
1179}
1180
1181static gpointer
1182sessionCloseThreadFunc( gpointer gdata )
1183{
1184    /* since tr_sessionClose() is a blocking function,
1185     * call it from another thread... when it's done,
1186     * punt the GUI teardown back to the GTK+ thread */
1187    struct cbdata * cbdata = gdata;
1188    gdk_threads_enter( );
1189    tr_core_close( cbdata->core );
1190    gtr_idle_add( onSessionClosed, gdata );
1191    gdk_threads_leave( );
1192    return NULL;
1193}
1194
1195static void
1196exit_now_cb( GtkWidget *w UNUSED, gpointer data UNUSED )
1197{
1198    exit( 0 );
1199}
1200
1201static void
1202wannaquit( gpointer vdata )
1203{
1204    GtkWidget *r, *p, *b, *w, *c;
1205    struct cbdata *cbdata = vdata;
1206
1207    /* stop the update timer */
1208    if( cbdata->timer )
1209    {
1210        g_source_remove( cbdata->timer );
1211        cbdata->timer = 0;
1212    }
1213
1214    c = GTK_WIDGET( cbdata->wind );
1215    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
1216
1217    r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
1218    gtk_container_add( GTK_CONTAINER( c ), r );
1219
1220    p = gtk_table_new( 3, 2, FALSE );
1221    gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
1222    gtk_container_add( GTK_CONTAINER( r ), p );
1223
1224    w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
1225    gtk_table_attach_defaults( GTK_TABLE( p ), w, 0, 1, 0, 2 );
1226
1227    w = gtk_label_new( NULL );
1228    gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
1229    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1230    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
1231
1232    w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
1233    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1234    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
1235
1236    b = gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
1237    w = gtk_button_new_with_mnemonic( _( "_Quit Now" ) );
1238    g_signal_connect( w, "clicked", G_CALLBACK( exit_now_cb ), NULL );
1239    gtk_container_add( GTK_CONTAINER( b ), w );
1240    gtk_table_attach( GTK_TABLE( p ), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
1241
1242    gtk_widget_show_all( r );
1243    gtk_widget_grab_focus( w );
1244
1245    /* clear the UI */
1246    tr_core_clear( cbdata->core );
1247
1248    /* ensure the window is in its previous position & size.
1249     * this seems to be necessary because changing the main window's
1250     * child seems to unset the size */
1251    gtk_window_resize( cbdata->wind, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH ),
1252                                     gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT ) );
1253    gtk_window_move( cbdata->wind, gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_X ),
1254                                   gtr_pref_int_get( PREF_KEY_MAIN_WINDOW_Y ) );
1255
1256    /* shut down libT */
1257    g_thread_create( sessionCloseThreadFunc, vdata, TRUE, NULL );
1258}
1259
1260static void
1261show_torrent_errors( GtkWindow * window, const char * primary, GSList ** files )
1262{
1263    GSList * l;
1264    GtkWidget * w;
1265    GString * s = g_string_new( NULL );
1266    const char * leader = g_slist_length( *files ) > 1
1267                        ? gtr_get_unicode_string( GTR_UNICODE_BULLET )
1268                        : "";
1269
1270    for( l=*files; l!=NULL; l=l->next )
1271        g_string_append_printf( s, "%s %s\n", leader, (const char*)l->data );
1272
1273    w = gtk_message_dialog_new( window,
1274                                GTK_DIALOG_DESTROY_WITH_PARENT,
1275                                GTK_MESSAGE_ERROR,
1276                                GTK_BUTTONS_CLOSE,
1277                                "%s", primary );
1278    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
1279                                              "%s", s->str );
1280    g_signal_connect_swapped( w, "response",
1281                              G_CALLBACK( gtk_widget_destroy ), w );
1282    gtk_widget_show( w );
1283    g_string_free( s, TRUE );
1284
1285    g_slist_foreach( *files, (GFunc)g_free, NULL );
1286    g_slist_free( *files );
1287    *files = NULL;
1288}
1289
1290static void
1291flush_torrent_errors( struct cbdata * cbdata )
1292{
1293    if( cbdata->error_list )
1294        show_torrent_errors( cbdata->wind,
1295                              gtr_ngettext( "Couldn't add corrupt torrent",
1296                                            "Couldn't add corrupt torrents",
1297                                            g_slist_length( cbdata->error_list ) ),
1298                              &cbdata->error_list );
1299
1300    if( cbdata->duplicates_list )
1301        show_torrent_errors( cbdata->wind,
1302                              gtr_ngettext( "Couldn't add duplicate torrent",
1303                                            "Couldn't add duplicate torrents",
1304                                            g_slist_length( cbdata->duplicates_list ) ),
1305                              &cbdata->duplicates_list );
1306}
1307
1308static void
1309on_core_error( TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c )
1310{
1311    switch( code )
1312    {
1313        case TR_PARSE_ERR:
1314            c->error_list =
1315                g_slist_append( c->error_list, g_path_get_basename( msg ) );
1316            break;
1317
1318        case TR_PARSE_DUPLICATE:
1319            c->duplicates_list = g_slist_append( c->duplicates_list, g_strdup( msg ) );
1320            break;
1321
1322        case TR_CORE_ERR_NO_MORE_TORRENTS:
1323            flush_torrent_errors( c );
1324            break;
1325
1326        default:
1327            g_assert_not_reached( );
1328            break;
1329    }
1330}
1331
1332#if GTK_CHECK_VERSION( 2, 8, 0 )
1333static gboolean
1334on_main_window_focus_in( GtkWidget      * widget UNUSED,
1335                         GdkEventFocus  * event  UNUSED,
1336                         gpointer                gdata )
1337{
1338    struct cbdata * cbdata = gdata;
1339
1340    if( cbdata->wind )
1341        gtk_window_set_urgency_hint( cbdata->wind, FALSE );
1342    return FALSE;
1343}
1344
1345#endif
1346
1347static void
1348on_add_torrent( TrCore * core, tr_ctor * ctor, gpointer gdata )
1349{
1350    struct cbdata * cbdata = gdata;
1351    GtkWidget * w = gtr_torrent_options_dialog_new( cbdata->wind, core, ctor );
1352
1353#if GTK_CHECK_VERSION( 2, 8, 0 )
1354    g_signal_connect( w, "focus-in-event",
1355                      G_CALLBACK( on_main_window_focus_in ),  cbdata );
1356    if( cbdata->wind )
1357        gtk_window_set_urgency_hint( cbdata->wind, TRUE );
1358#endif
1359
1360    gtk_widget_show( w );
1361}
1362
1363static void
1364on_prefs_changed( TrCore * core UNUSED, const char * key, gpointer data )
1365{
1366    struct cbdata * cbdata = data;
1367    tr_session * tr = tr_core_session( cbdata->core );
1368
1369    if( !strcmp( key, TR_PREFS_KEY_ENCRYPTION ) )
1370    {
1371        tr_sessionSetEncryption( tr, gtr_pref_int_get( key ) );
1372    }
1373    else if( !strcmp( key, TR_PREFS_KEY_DOWNLOAD_DIR ) )
1374    {
1375        tr_sessionSetDownloadDir( tr, gtr_pref_string_get( key ) );
1376    }
1377    else if( !strcmp( key, TR_PREFS_KEY_MSGLEVEL ) )
1378    {
1379        tr_setMessageLevel( gtr_pref_int_get( key ) );
1380    }
1381    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
1382    {
1383        tr_sessionSetPeerPort( tr, gtr_pref_int_get( key ) );
1384    }
1385    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_ENABLED ) )
1386    {
1387        tr_blocklistSetEnabled( tr, gtr_pref_flag_get( key ) );
1388    }
1389    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_URL ) )
1390    {
1391        tr_blocklistSetURL( tr, gtr_pref_string_get( key ) );
1392    }
1393    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
1394    {
1395        const int show = gtr_pref_flag_get( key );
1396        if( show && !cbdata->icon )
1397            cbdata->icon = gtr_icon_new( cbdata->core );
1398        else if( !show && cbdata->icon ) {
1399            g_object_unref( cbdata->icon );
1400            cbdata->icon = NULL;
1401        }
1402    }
1403    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_ENABLED ) )
1404    {
1405        tr_sessionLimitSpeed( tr, TR_DOWN, gtr_pref_flag_get( key ) );
1406    }
1407    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_KBps ) )
1408    {
1409        tr_sessionSetSpeedLimit_KBps( tr, TR_DOWN, gtr_pref_int_get( key ) );
1410    }
1411    else if( !strcmp( key, TR_PREFS_KEY_USPEED_ENABLED ) )
1412    {
1413        tr_sessionLimitSpeed( tr, TR_UP, gtr_pref_flag_get( key ) );
1414    }
1415    else if( !strcmp( key, TR_PREFS_KEY_USPEED_KBps ) )
1416    {
1417        tr_sessionSetSpeedLimit_KBps( tr, TR_UP, gtr_pref_int_get( key ) );
1418    }
1419    else if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) )
1420    {
1421        tr_sessionSetRatioLimited( tr, gtr_pref_flag_get( key ) );
1422    }
1423    else if( !strcmp( key, TR_PREFS_KEY_RATIO ) )
1424    {
1425        tr_sessionSetRatioLimit( tr, gtr_pref_double_get( key ) );
1426    }
1427    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT ) )
1428    {
1429        tr_sessionSetIdleLimit( tr, gtr_pref_int_get( key ) );
1430    }
1431    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT_ENABLED ) )
1432    {
1433        tr_sessionSetIdleLimited( tr, gtr_pref_flag_get( key ) );
1434    }
1435    else if( !strcmp( key, TR_PREFS_KEY_PORT_FORWARDING ) )
1436    {
1437        tr_sessionSetPortForwardingEnabled( tr, gtr_pref_flag_get( key ) );
1438    }
1439    else if( !strcmp( key, TR_PREFS_KEY_PEX_ENABLED ) )
1440    {
1441        tr_sessionSetPexEnabled( tr, gtr_pref_flag_get( key ) );
1442    }
1443    else if( !strcmp( key, TR_PREFS_KEY_RENAME_PARTIAL_FILES ) )
1444    {
1445        tr_sessionSetIncompleteFileNamingEnabled( tr, gtr_pref_flag_get( key ) );
1446    }
1447    else if( !strcmp( key, TR_PREFS_KEY_DHT_ENABLED ) )
1448    {
1449        tr_sessionSetDHTEnabled( tr, gtr_pref_flag_get( key ) );
1450    }
1451    else if( !strcmp( key, TR_PREFS_KEY_LPD_ENABLED ) )
1452    {
1453        tr_sessionSetLPDEnabled( tr, gtr_pref_flag_get( key ) );
1454    }
1455    else if( !strcmp( key, TR_PREFS_KEY_RPC_PORT ) )
1456    {
1457        tr_sessionSetRPCPort( tr, gtr_pref_int_get( key ) );
1458    }
1459    else if( !strcmp( key, TR_PREFS_KEY_RPC_ENABLED ) )
1460    {
1461        tr_sessionSetRPCEnabled( tr, gtr_pref_flag_get( key ) );
1462    }
1463    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST ) )
1464    {
1465        tr_sessionSetRPCWhitelist( tr, gtr_pref_string_get( key ) );
1466    }
1467    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED ) )
1468    {
1469        tr_sessionSetRPCWhitelistEnabled( tr, gtr_pref_flag_get( key ) );
1470    }
1471    else if( !strcmp( key, TR_PREFS_KEY_RPC_USERNAME ) )
1472    {
1473        tr_sessionSetRPCUsername( tr, gtr_pref_string_get( key ) );
1474    }
1475    else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
1476    {
1477        tr_sessionSetRPCPassword( tr, gtr_pref_string_get( key ) );
1478    }
1479    else if( !strcmp( key, TR_PREFS_KEY_RPC_AUTH_REQUIRED ) )
1480    {
1481        tr_sessionSetRPCPasswordEnabled( tr, gtr_pref_flag_get( key ) );
1482    }
1483    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_UP_KBps ) )
1484    {
1485        tr_sessionSetAltSpeed_KBps( tr, TR_UP, gtr_pref_int_get( key ) );
1486    }
1487    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps ) )
1488    {
1489        tr_sessionSetAltSpeed_KBps( tr, TR_DOWN, gtr_pref_int_get( key ) );
1490    }
1491    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) )
1492    {
1493        const gboolean b = gtr_pref_flag_get( key );
1494        tr_sessionUseAltSpeed( tr, b );
1495        gtr_action_set_toggled( key, b );
1496    }
1497    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN ) )
1498    {
1499        tr_sessionSetAltSpeedBegin( tr, gtr_pref_int_get( key ) );
1500    }
1501    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_END ) )
1502    {
1503        tr_sessionSetAltSpeedEnd( tr, gtr_pref_int_get( key ) );
1504    }
1505    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED ) )
1506    {
1507        tr_sessionUseAltSpeedTime( tr, gtr_pref_flag_get( key ) );
1508    }
1509    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_DAY ) )
1510    {
1511        tr_sessionSetAltSpeedDay( tr, gtr_pref_int_get( key ) );
1512    }
1513    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START ) )
1514    {
1515        tr_sessionSetPeerPortRandomOnStart( tr, gtr_pref_flag_get( key ) );
1516    }
1517    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR ) )
1518    {
1519        tr_sessionSetIncompleteDir( tr, gtr_pref_string_get( key ) );
1520    }
1521    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED ) )
1522    {
1523        tr_sessionSetIncompleteDirEnabled( tr, gtr_pref_flag_get( key ) );
1524    }
1525    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED ) )
1526    {
1527        tr_sessionSetTorrentDoneScriptEnabled( tr, gtr_pref_flag_get( key ) );
1528    }
1529    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME ) )
1530    {
1531        tr_sessionSetTorrentDoneScript( tr, gtr_pref_string_get( key ) );
1532    }
1533    else if( !strcmp( key, TR_PREFS_KEY_START) )
1534    {
1535        tr_sessionSetPaused( tr, !gtr_pref_flag_get( key ) );
1536    }
1537    else if( !strcmp( key, TR_PREFS_KEY_TRASH_ORIGINAL ) )
1538    {
1539        tr_sessionSetDeleteSource( tr, gtr_pref_flag_get( key ) );
1540    }
1541}
1542
1543static gboolean
1544updatemodel( gpointer gdata )
1545{
1546    struct cbdata *data = gdata;
1547    const gboolean done = data->isClosing || global_sigcount;
1548
1549    if( !done )
1550    {
1551        /* update the torrent data in the model */
1552        tr_core_update( data->core );
1553
1554        /* refresh the main window's statusbar and toolbar buttons */
1555        if( data->wind != NULL )
1556            gtr_window_refresh( data->wind );
1557
1558        /* update the actions */
1559        refresh_actions( data );
1560
1561        /* update the status tray icon */
1562        if( data->icon != NULL )
1563            gtr_icon_refresh( data->icon );
1564    }
1565
1566    return !done;
1567}
1568
1569static void
1570onUriClicked( GtkAboutDialog * u UNUSED, const gchar * uri, gpointer u2 UNUSED )
1571{
1572    gtr_open_uri( uri );
1573}
1574
1575static void
1576about( GtkWindow * parent )
1577{
1578    GtkWidget * d;
1579    const char * website_uri = "http://www.transmissionbt.com/";
1580    const char * authors[] = {
1581        "Jordan Lee (Backend; GTK+)",
1582        "Mitchell Livingston (Backend; OS X)",
1583        NULL
1584    };
1585
1586    gtk_about_dialog_set_url_hook( onUriClicked, NULL, NULL );
1587
1588    d = g_object_new( GTK_TYPE_ABOUT_DIALOG,
1589                      "authors", authors,
1590                      "comments", _( "A fast and easy BitTorrent client" ),
1591                      "copyright", _( "Copyright (c) The Transmission Project" ),
1592                      "logo-icon-name", MY_CONFIG_NAME,
1593                      "name", g_get_application_name( ),
1594                      /* Translators: translate "translator-credits" as your name
1595                         to have it appear in the credits in the "About"
1596                         dialog */
1597                      "translator-credits", _( "translator-credits" ),
1598                      "version", LONG_VERSION_STRING,
1599                      "website", website_uri,
1600                      "website-label", website_uri,
1601#ifdef SHOW_LICENSE
1602                      "license", LICENSE,
1603                      "wrap-license", TRUE,
1604#endif
1605                      NULL );
1606    gtk_window_set_transient_for( GTK_WINDOW( d ), parent );
1607    g_signal_connect_swapped( d, "response", G_CALLBACK (gtk_widget_destroy), d );
1608    gtk_widget_show( d );
1609}
1610
1611static void
1612appendIdToBencList( GtkTreeModel * m, GtkTreePath * path UNUSED,
1613                    GtkTreeIter * iter, gpointer list )
1614{
1615    tr_torrent * tor = NULL;
1616    gtk_tree_model_get( m, iter, MC_TORRENT_RAW, &tor, -1 );
1617    tr_bencListAddInt( list, tr_torrentId( tor ) );
1618}
1619
1620static gboolean
1621rpcOnSelectedTorrents( struct cbdata * data, const char * method )
1622{
1623    tr_benc top, *args, *ids;
1624    gboolean invoked = FALSE;
1625    GtkTreeSelection * s = data->sel;
1626    tr_session * session = tr_core_session( data->core );
1627
1628    tr_bencInitDict( &top, 2 );
1629    tr_bencDictAddStr( &top, "method", method );
1630    args = tr_bencDictAddDict( &top, "arguments", 1 );
1631    ids = tr_bencDictAddList( args, "ids", 0 );
1632    gtk_tree_selection_selected_foreach( s, appendIdToBencList, ids );
1633
1634    if( tr_bencListSize( ids ) != 0 )
1635    {
1636        int json_len;
1637        char * json = tr_bencToStr( &top, TR_FMT_JSON_LEAN, &json_len );
1638        tr_rpc_request_exec_json( session, json, json_len, NULL, NULL );
1639        g_free( json );
1640        invoked = TRUE;
1641    }
1642
1643    tr_bencFree( &top );
1644    return invoked;
1645}
1646
1647static void
1648openFolderForeach( GtkTreeModel * model, GtkTreePath * path UNUSED,
1649                   GtkTreeIter * iter, gpointer user_data UNUSED )
1650{
1651    TrTorrent * gtor = NULL;
1652
1653    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1654    tr_torrent_open_folder( gtor );
1655    g_object_unref( G_OBJECT( gtor ) );
1656}
1657
1658static gboolean
1659msgwinclosed( void )
1660{
1661    gtr_action_set_toggled( "toggle-message-log", FALSE );
1662    return FALSE;
1663}
1664
1665static void
1666accumulateSelectedTorrents( GtkTreeModel * model, GtkTreePath * path UNUSED,
1667                            GtkTreeIter * iter, gpointer gdata )
1668{
1669    GSList ** data = ( GSList** ) gdata;
1670    TrTorrent * gtor = NULL;
1671
1672    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1673    *data = g_slist_append( *data, gtor );
1674    g_object_unref( G_OBJECT( gtor ) );
1675}
1676
1677static void
1678removeSelected( struct cbdata * data, gboolean delete_files )
1679{
1680    GSList * l = NULL;
1681
1682    gtk_tree_selection_selected_foreach( data->sel, accumulateSelectedTorrents, &l );
1683
1684    if( l != NULL )
1685        gtr_confirm_remove( data->wind, data->core, l, delete_files );
1686}
1687
1688static void
1689startAllTorrents( struct cbdata * data )
1690{
1691    tr_session * session = tr_core_session( data->core );
1692    const char * cmd = "{ \"method\": \"torrent-start\" }";
1693    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1694}
1695
1696static void
1697pauseAllTorrents( struct cbdata * data )
1698{
1699    tr_session * session = tr_core_session( data->core );
1700    const char * cmd = "{ \"method\": \"torrent-stop\" }";
1701    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1702}
1703
1704static tr_torrent*
1705getFirstSelectedTorrent( struct cbdata * data )
1706{
1707    tr_torrent * tor = NULL;
1708    GtkTreeModel * m;
1709    GList * l = gtk_tree_selection_get_selected_rows( data->sel, &m );
1710    if( l != NULL ) {
1711        GtkTreePath * p = l->data;
1712        GtkTreeIter i;
1713        if( gtk_tree_model_get_iter( m, &i, p ) )
1714            gtk_tree_model_get( m, &i, MC_TORRENT_RAW, &tor, -1 );
1715    }
1716    g_list_foreach( l, (GFunc)gtk_tree_path_free, NULL );
1717    g_list_free( l );
1718    return tor;
1719}
1720
1721static void
1722copyMagnetLinkToClipboard( GtkWidget * w, tr_torrent * tor )
1723{
1724    char * magnet = tr_torrentGetMagnetLink( tor );
1725    GdkDisplay * display = gtk_widget_get_display( w );
1726    GdkAtom selection;
1727    GtkClipboard * clipboard;
1728
1729    /* this is The Right Thing for copy/paste... */
1730    selection = GDK_SELECTION_CLIPBOARD;
1731    clipboard = gtk_clipboard_get_for_display( display, selection );
1732    gtk_clipboard_set_text( clipboard, magnet, -1 );
1733
1734    /* ...but people using plain ol' X need this instead */
1735    selection = GDK_SELECTION_PRIMARY;
1736    clipboard = gtk_clipboard_get_for_display( display, selection );
1737    gtk_clipboard_set_text( clipboard, magnet, -1 );
1738
1739    /* cleanup */
1740    tr_free( magnet );
1741}
1742
1743void
1744gtr_actions_handler( const char * action_name, gpointer user_data )
1745{
1746    struct cbdata * data = user_data;
1747    gboolean        changed = FALSE;
1748
1749    if( !strcmp( action_name, "open-torrent-from-url" ) )
1750    {
1751        GtkWidget * w = gtr_torrent_open_from_url_dialog_new( data->wind, data->core );
1752        gtk_widget_show( w );
1753    }
1754    else if(  !strcmp( action_name, "open-torrent-menu" )
1755      || !strcmp( action_name, "open-torrent-toolbar" ) )
1756    {
1757        GtkWidget * w = gtr_torrent_open_from_file_dialog_new( data->wind, data->core );
1758        gtk_widget_show( w );
1759    }
1760    else if( !strcmp( action_name, "show-stats" ) )
1761    {
1762        GtkWidget * dialog = gtr_stats_dialog_new( data->wind, data->core );
1763        gtk_widget_show( dialog );
1764    }
1765    else if( !strcmp( action_name, "donate" ) )
1766    {
1767        gtr_open_uri( "http://www.transmissionbt.com/donate.php" );
1768    }
1769    else if( !strcmp( action_name, "pause-all-torrents" ) )
1770    {
1771        pauseAllTorrents( data );
1772    }
1773    else if( !strcmp( action_name, "start-all-torrents" ) )
1774    {
1775        startAllTorrents( data );
1776    }
1777    else if( !strcmp( action_name, "copy-magnet-link-to-clipboard" ) )
1778    {
1779        tr_torrent * tor = getFirstSelectedTorrent( data );
1780        if( tor != NULL )
1781        {
1782            copyMagnetLinkToClipboard( GTK_WIDGET( data->wind ), tor );
1783        }
1784    }
1785    else if( !strcmp( action_name, "relocate-torrent" ) )
1786    {
1787        GSList * ids = getSelectedTorrentIds( data );
1788        if( ids != NULL )
1789        {
1790            GtkWindow * parent = data->wind;
1791            GtkWidget * w = gtr_relocate_dialog_new( parent, data->core, ids );
1792            gtk_widget_show( w );
1793        }
1794    }
1795    else if( !strcmp( action_name, "start-torrent" ) )
1796    {
1797        changed |= rpcOnSelectedTorrents( data, "torrent-start" );
1798    }
1799    else if( !strcmp( action_name, "pause-torrent" ) )
1800    {
1801        changed |= rpcOnSelectedTorrents( data, "torrent-stop" );
1802    }
1803    else if( !strcmp( action_name, "verify-torrent" ) )
1804    {
1805        changed |= rpcOnSelectedTorrents( data, "torrent-verify" );
1806    }
1807    else if( !strcmp( action_name, "update-tracker" ) )
1808    {
1809        changed |= rpcOnSelectedTorrents( data, "torrent-reannounce" );
1810    }
1811    else if( !strcmp( action_name, "open-torrent-folder" ) )
1812    {
1813        gtk_tree_selection_selected_foreach( data->sel, openFolderForeach, data );
1814    }
1815    else if( !strcmp( action_name, "show-torrent-properties" ) )
1816    {
1817        show_details_dialog_for_selected_torrents( data );
1818    }
1819    else if( !strcmp( action_name, "new-torrent" ) )
1820    {
1821        GtkWidget * w = gtr_torrent_creation_dialog_new( data->wind, data->core );
1822        gtk_widget_show( w );
1823    }
1824    else if( !strcmp( action_name, "remove-torrent" ) )
1825    {
1826        removeSelected( data, FALSE );
1827    }
1828    else if( !strcmp( action_name, "delete-torrent" ) )
1829    {
1830        removeSelected( data, TRUE );
1831    }
1832    else if( !strcmp( action_name, "quit" ) )
1833    {
1834        maybeaskquit( data );
1835    }
1836    else if( !strcmp( action_name, "select-all" ) )
1837    {
1838        gtk_tree_selection_select_all( data->sel );
1839    }
1840    else if( !strcmp( action_name, "deselect-all" ) )
1841    {
1842        gtk_tree_selection_unselect_all( data->sel );
1843    }
1844    else if( !strcmp( action_name, "edit-preferences" ) )
1845    {
1846        if( NULL == data->prefs )
1847        {
1848            data->prefs = gtr_prefs_dialog_new( data->wind, G_OBJECT( data->core ) );
1849            g_signal_connect( data->prefs, "destroy",
1850                              G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1851            gtk_widget_show( GTK_WIDGET( data->prefs ) );
1852        }
1853    }
1854    else if( !strcmp( action_name, "toggle-message-log" ) )
1855    {
1856        if( !data->msgwin )
1857        {
1858            GtkWidget * win = gtr_message_log_window_new( data->wind, data->core );
1859            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ), NULL );
1860            data->msgwin = win;
1861        }
1862        else
1863        {
1864            gtr_action_set_toggled( "toggle-message-log", FALSE );
1865            gtk_widget_destroy( data->msgwin );
1866            data->msgwin = NULL;
1867        }
1868    }
1869    else if( !strcmp( action_name, "show-about-dialog" ) )
1870    {
1871        about( data->wind );
1872    }
1873    else if( !strcmp ( action_name, "help" ) )
1874    {
1875        gtr_open_uri( gtr_get_help_uri( ) );
1876    }
1877    else if( !strcmp( action_name, "toggle-main-window" ) )
1878    {
1879        toggleMainWindow( data );
1880    }
1881    else g_error ( "Unhandled action: %s", action_name );
1882
1883    if( changed )
1884        updatemodel( data );
1885}
Note: See TracBrowser for help on using the repository browser.