source: trunk/gtk/main.c @ 11834

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

(trunk) #3675 "Not all .part files are removed" -- handle trashing files via RPC.

When libtransmission gets a "remove torrent" request from RPC, it tries to delegate the work. This is because the GTK+ and Mac clients don't want torrents disappearing in a different thread and causing possible thread issues. So the GTK+ and Mac clients get notification about this via libtransmission's RPC callback and remove the torrents themselves. Unfortunately, that notification doesn't include information about whether or not to delete local data.

This commit adds that information to the RPC callback so that the Mac and GTK+ clients will know whether or not to trash the local files when a third-party RPC client requests that at torrent and its files be deleted.

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