source: branches/2.2x/gtk/main.c @ 12067

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

(2.2x gtk) remove dead code: cbdata.isIconified

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