source: trunk/gtk/main.c @ 8172

Last change on this file since 8172 was 8172, checked in by charles, 12 years ago

(gtk trunk) better handling of the random port code, thanks to feedback from Rolcol

  • Property svn:keywords set to Date Rev Author Id
File size: 45.6 KB
Line 
1/******************************************************************************
2 * $Id: main.c 8172 2009-04-07 04:30:16Z charles $
3 *
4 * Copyright (c) 2005-2008 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 <sys/param.h>
27#include <signal.h>
28#include <string.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <time.h>
32#include <unistd.h>
33
34#include <gtk/gtk.h>
35#include <glib/gi18n.h>
36#include <glib/gstdio.h>
37
38#include <libtransmission/transmission.h>
39#include <libtransmission/rpcimpl.h>
40#include <libtransmission/utils.h>
41#include <libtransmission/version.h>
42
43#include "actions.h"
44#include "add-dialog.h"
45#include "blocklist.h"
46#include "conf.h"
47#include "details.h"
48#include "dialogs.h"
49#include "hig.h"
50#include "makemeta-ui.h"
51#include "msgwin.h"
52#include "notify.h"
53#include "stats.h"
54#include "tr-core.h"
55#include "tr-icon.h"
56#include "tr-prefs.h"
57#include "tr-torrent.h"
58#include "tr-window.h"
59#include "util.h"
60#include "ui.h"
61
62#define MY_NAME "transmission"
63
64#define REFRESH_INTERVAL_SECONDS 2
65
66#if GTK_CHECK_VERSION( 2, 8, 0 )
67 #define SHOW_LICENSE
68static const char * LICENSE =
69    "The Transmission binaries and most of its source code is distributed "
70    "license. "
71    "\n\n"
72    "Some files are copyrighted by Charles Kerr and are covered by "
73    "the GPL version 2.  Works owned by the Transmission project "
74    "are granted a special exemption to clause 2(b) so that the bulk "
75    "of its code can remain under the MIT license.  This exemption does "
76    "not extend to original or derived works not owned by the "
77    "Transmission project. "
78    "\n\n"
79    "Permission is hereby granted, free of charge, to any person obtaining "
80    "a copy of this software and associated documentation files (the "
81    "'Software'), to deal in the Software without restriction, including "
82    "without limitation the rights to use, copy, modify, merge, publish, "
83    "distribute, sublicense, and/or sell copies of the Software, and to "
84    "permit persons to whom the Software is furnished to do so, subject to "
85    "the following conditions: "
86    "\n\n"
87    "The above copyright notice and this permission notice shall be included "
88    "in all copies or substantial portions of the Software. "
89    "\n\n"
90    "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, "
91    "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF "
92    "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. "
93    "IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY "
94    "CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, "
95    "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE "
96    "SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.";
97#endif
98
99struct cbdata
100{
101    unsigned int  isIconified : 1;
102    unsigned int  isClosing   : 1;
103    guint         timer;
104    gpointer      icon;
105    GtkWindow *   wind;
106    TrCore *      core;
107    GtkWidget *   msgwin;
108    GtkWidget *   prefs;
109    GSList *      errqueue;
110    GSList *      dupqueue;
111    GHashTable *  tor2details;
112    GHashTable *  details2tor;
113    GtkTreeSelection * sel;
114};
115
116#define CBDATA_PTR "callback-data-pointer"
117
118static GtkUIManager * myUIManager = NULL;
119
120static void           appsetup( TrWindow * wind,
121                                GSList *   args,
122                                struct     cbdata *,
123                                gboolean   paused,
124                                gboolean   minimized );
125
126static void           winsetup( struct cbdata * cbdata,
127                                TrWindow *      wind );
128
129static void           wannaquit( void * vdata );
130
131static void           setupdrag( GtkWidget *    widget,
132                                 struct cbdata *data );
133
134static void           gotdrag( GtkWidget *       widget,
135                               GdkDragContext *  dc,
136                               gint              x,
137                               gint              y,
138                               GtkSelectionData *sel,
139                               guint             info,
140                               guint             time,
141                               gpointer          gdata );
142
143static void           coreerr( TrCore *         core,
144                               enum tr_core_err code,
145                               const char *     msg,
146                               gpointer         gdata );
147
148static void           onAddTorrent( TrCore *,
149                                    tr_ctor *,
150                                    gpointer );
151
152static void           prefschanged( TrCore *     core,
153                                    const char * key,
154                                    gpointer     data );
155
156static gboolean       updatemodel( gpointer gdata );
157
158struct counts_data
159{
160    int    totalCount;
161    int    activeCount;
162    int    inactiveCount;
163};
164
165static void
166accumulateStatusForeach( GtkTreeModel *      model,
167                         GtkTreePath  * path UNUSED,
168                         GtkTreeIter *       iter,
169                         gpointer            user_data )
170{
171    int                  activity = 0;
172    struct counts_data * counts = user_data;
173
174    ++counts->totalCount;
175
176    gtk_tree_model_get( model, iter, MC_ACTIVITY, &activity, -1 );
177
178    if( TR_STATUS_IS_ACTIVE( activity ) )
179        ++counts->activeCount;
180    else
181        ++counts->inactiveCount;
182}
183
184static void
185accumulateCanUpdateForeach( GtkTreeModel *      model,
186                            GtkTreePath  * path UNUSED,
187                            GtkTreeIter *       iter,
188                            gpointer            accumulated_status )
189{
190    tr_torrent * tor;
191    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
192    *(int*)accumulated_status |= tr_torrentCanManualUpdate( tor );
193}
194
195static void
196refreshTorrentActions( struct cbdata * data )
197{
198    int canUpdate;
199    struct counts_data counts;
200    GtkTreeSelection * s = data->sel;
201
202    counts.activeCount = 0;
203    counts.inactiveCount = 0;
204    counts.totalCount = 0;
205    gtk_tree_selection_selected_foreach( s, accumulateStatusForeach, &counts );
206    action_sensitize( "pause-torrent", counts.activeCount != 0 );
207    action_sensitize( "start-torrent", counts.inactiveCount != 0 );
208    action_sensitize( "remove-torrent", counts.totalCount != 0 );
209    action_sensitize( "delete-torrent", counts.totalCount != 0 );
210    action_sensitize( "verify-torrent", counts.totalCount != 0 );
211    action_sensitize( "open-torrent-folder", counts.totalCount == 1 );
212    action_sensitize( "show-torrent-properties", counts.totalCount == 1 );
213
214    canUpdate = 0;
215    gtk_tree_selection_selected_foreach( s, accumulateCanUpdateForeach, &canUpdate );
216    action_sensitize( "update-tracker", canUpdate != 0 );
217
218    {
219        GtkTreeView *  view = gtk_tree_selection_get_tree_view( s );
220        GtkTreeModel * model = gtk_tree_view_get_model( view );
221        const int torrentCount = gtk_tree_model_iter_n_children( model, NULL ) != 0;
222        action_sensitize( "select-all", torrentCount != 0 );
223        action_sensitize( "deselect-all", torrentCount != 0 );
224    }
225
226    {
227        tr_session * session = tr_core_session( data->core );
228        const int active = tr_sessionGetActiveTorrentCount( session );
229        const int total = tr_sessionCountTorrents( session );
230        action_sensitize( "pause-all-torrents", active != 0 );
231        action_sensitize( "start-all-torrents", active != total );
232    }
233}
234
235static void
236selectionChangedCB( GtkTreeSelection * s UNUSED, gpointer data )
237{
238    refreshTorrentActions( data );
239}
240
241static void
242onMainWindowSizeAllocated( GtkWidget *            window,
243                           GtkAllocation  * alloc UNUSED,
244                           gpointer         gdata UNUSED )
245{
246    const gboolean isMaximized = window->window
247                            && ( gdk_window_get_state( window->window )
248                                 & GDK_WINDOW_STATE_MAXIMIZED );
249
250    if( !isMaximized )
251    {
252        int x, y, w, h;
253        gtk_window_get_position( GTK_WINDOW( window ), &x, &y );
254        gtk_window_get_size( GTK_WINDOW( window ), &w, &h );
255        pref_int_set( PREF_KEY_MAIN_WINDOW_X, x );
256        pref_int_set( PREF_KEY_MAIN_WINDOW_Y, y );
257        pref_int_set( PREF_KEY_MAIN_WINDOW_WIDTH, w );
258        pref_int_set( PREF_KEY_MAIN_WINDOW_HEIGHT, h );
259    }
260}
261
262static sig_atomic_t global_sigcount = 0;
263
264static void
265fatalsig( int sig )
266{
267    /* revert to default handler after this many */
268    static const int SIGCOUNT_MAX = 3;
269
270    if( ++global_sigcount >= SIGCOUNT_MAX )
271    {
272        signal( sig, SIG_DFL );
273        raise( sig );
274    }
275}
276
277static void
278setupsighandlers( void )
279{
280#ifdef G_OS_WIN32
281    const int sigs[] = { SIGINT, SIGTERM };
282#else
283    const int sigs[] = { SIGHUP, SIGINT, SIGQUIT, SIGTERM };
284#endif
285    guint     i;
286
287    for( i = 0; i < G_N_ELEMENTS( sigs ); ++i )
288        signal( sigs[i], fatalsig );
289}
290
291static tr_rpc_callback_status
292onRPCChanged( tr_session            * session UNUSED,
293              tr_rpc_callback_type    type,
294              struct tr_torrent     * tor,
295              void                  * gdata )
296{
297    struct cbdata * cbdata = gdata;
298    gdk_threads_enter( );
299
300    switch( type )
301    {
302        case TR_RPC_TORRENT_ADDED:
303            tr_core_add_torrent( cbdata->core,
304                                 tr_torrent_new_preexisting( tor ) );
305            break;
306
307        case TR_RPC_TORRENT_STARTED:
308            /* this should be automatic */
309            break;
310
311        case TR_RPC_TORRENT_STOPPED:
312            /* this should be automatic */
313            break;
314
315        case TR_RPC_TORRENT_REMOVING:
316            tr_core_torrent_destroyed( cbdata->core, tr_torrentId( tor ) );
317            break;
318
319        case TR_RPC_TORRENT_CHANGED:
320        case TR_RPC_SESSION_CHANGED:
321            /* nothing interesting to do here */
322            break;
323    }
324
325    gdk_threads_leave( );
326    return TR_RPC_OK;
327}
328
329int
330main( int     argc,
331      char ** argv )
332{
333    char *              err = NULL;
334    struct cbdata *     cbdata;
335    GSList *            argfiles;
336    GError *            gerr;
337    gboolean            didinit = FALSE;
338    gboolean            didlock = FALSE;
339    gboolean            showversion = FALSE;
340    gboolean            startpaused = FALSE;
341    gboolean            startminimized = FALSE;
342    const char *        domain = MY_NAME;
343    char *              configDir = NULL;
344    tr_lockfile_state_t tr_state;
345
346    GOptionEntry entries[] = {
347        { "paused",     'p', 0, G_OPTION_ARG_NONE,
348          &startpaused, _( "Start with all torrents paused" ), NULL },
349        { "version",    '\0', 0, G_OPTION_ARG_NONE,
350          &showversion, _( "Show version number and exit" ), NULL },
351#ifdef STATUS_ICON_SUPPORTED
352        { "minimized",  'm', 0, G_OPTION_ARG_NONE,
353          &startminimized,
354          _( "Start minimized in system tray" ), NULL },
355#endif
356        { "config-dir", 'g', 0, G_OPTION_ARG_FILENAME, &configDir,
357          _( "Where to look for configuration files" ), NULL },
358        { NULL, 0,   0, 0, NULL, NULL, NULL }
359    };
360
361    cbdata = g_new0( struct cbdata, 1 );
362    cbdata->tor2details = g_hash_table_new( g_str_hash, g_str_equal );
363    cbdata->details2tor = g_hash_table_new( g_direct_hash, g_direct_equal );
364
365    /* bind the gettext domain */
366    setlocale( LC_ALL, "" );
367    bindtextdomain( domain, TRANSMISSIONLOCALEDIR );
368    bind_textdomain_codeset( domain, "UTF-8" );
369    textdomain( domain );
370    g_set_application_name( _( "Transmission" ) );
371
372    /* initialize gtk */
373    if( !g_thread_supported( ) )
374        g_thread_init( NULL );
375
376    gerr = NULL;
377    if( !gtk_init_with_args( &argc, &argv, _( "[torrent files]" ), entries,
378                             (char*)domain, &gerr ) )
379    {
380        fprintf( stderr, "%s\n", gerr->message );
381        g_clear_error( &gerr );
382        return 0;
383    }
384
385    if( showversion )
386    {
387        fprintf( stderr, "%s %s\n", g_get_application_name( ), LONG_VERSION_STRING );
388        return 0;
389    }
390
391    if( configDir == NULL )
392        configDir = (char*) tr_getDefaultConfigDir( MY_NAME );
393
394    tr_notify_init( );
395    didinit = cf_init( configDir, NULL ); /* must come before actions_init */
396
397    myUIManager = gtk_ui_manager_new ( );
398    actions_init ( myUIManager, cbdata );
399    gtk_ui_manager_add_ui_from_string ( myUIManager, fallback_ui_file, -1, NULL );
400    gtk_ui_manager_ensure_update ( myUIManager );
401    gtk_window_set_default_icon_name ( MY_NAME );
402
403    setupsighandlers( ); /* set up handlers for fatal signals */
404
405    /* either get a lockfile s.t. this is the one instance of
406     * transmission that's running, OR if there are files to
407     * be added, delegate that to the running instance via dbus */
408    didlock = cf_lock( &tr_state, &err );
409    argfiles = checkfilenames( argc - 1, argv + 1 );
410    if( !didlock && argfiles )
411    {
412        GSList * l;
413        gboolean delegated = FALSE;
414        for( l = argfiles; l; l = l->next )
415            delegated |= gtr_dbus_add_torrent( l->data );
416        if( delegated )
417            err = NULL;
418    }
419    else if( ( !didlock ) && ( tr_state == TR_LOCKFILE_ELOCK ) )
420    {
421        gtr_dbus_present_window( );
422        err = NULL;
423    }
424
425    if( didlock && ( didinit || cf_init( configDir, &err ) ) )
426    {
427        const char * str;
428        GtkWindow * win;
429        tr_session * session;
430
431        /* ensure the directories are created */
432       if(( str = pref_string_get( PREF_KEY_DIR_WATCH )))
433           mkdir_p( str, 0777 );
434       if(( str = pref_string_get( TR_PREFS_KEY_DOWNLOAD_DIR )))
435           mkdir_p( str, 0777 );
436
437        /* initialize the libtransmission session */
438        session = tr_sessionInit( "gtk", configDir, TRUE, pref_get_all( ) );
439        pref_flag_set( TR_PREFS_KEY_ALT_SPEED_ENABLED, tr_sessionUsesAltSpeed( session ) );
440        pref_int_set( TR_PREFS_KEY_PEER_PORT, tr_sessionGetPeerPort( session ) );
441        cbdata->core = tr_core_new( session );
442
443        /* create main window now to be a parent to any error dialogs */
444        win = GTK_WINDOW( tr_window_new( myUIManager, cbdata->core ) );
445        g_signal_connect( win, "size-allocate", G_CALLBACK( onMainWindowSizeAllocated ), cbdata );
446
447        appsetup( win, argfiles, cbdata, startpaused, startminimized );
448        tr_sessionSetRPCCallback( session, onRPCChanged, cbdata );
449        gtr_blocklist_maybe_autoupdate( cbdata->core );
450
451        gtk_main( );
452    }
453    else if( err )
454    {
455        gtk_widget_show( errmsg_full( NULL, (callbackfunc_t)gtk_main_quit,
456                                      NULL, "%s", err ) );
457        g_free( err );
458        gtk_main( );
459    }
460
461    return 0;
462}
463
464static void
465appsetup( TrWindow *      wind,
466          GSList *        torrentFiles,
467          struct cbdata * cbdata,
468          gboolean        forcepause,
469          gboolean        isIconified )
470{
471    const pref_flag_t start =
472        forcepause ? PREF_FLAG_FALSE : PREF_FLAG_DEFAULT;
473    const pref_flag_t prompt = PREF_FLAG_DEFAULT;
474
475    /* fill out cbdata */
476    cbdata->wind         = NULL;
477    cbdata->icon         = NULL;
478    cbdata->msgwin       = NULL;
479    cbdata->prefs        = NULL;
480    cbdata->timer        = 0;
481    cbdata->isClosing    = 0;
482    cbdata->errqueue     = NULL;
483    cbdata->dupqueue     = NULL;
484    cbdata->isIconified  = isIconified;
485
486    if( isIconified )
487        pref_flag_set( PREF_KEY_SHOW_TRAY_ICON, TRUE );
488
489    actions_set_core( cbdata->core );
490
491    /* set up core handlers */
492    g_signal_connect( cbdata->core, "error", G_CALLBACK( coreerr ), cbdata );
493    g_signal_connect( cbdata->core, "add-torrent-prompt",
494                      G_CALLBACK( onAddTorrent ), cbdata );
495    g_signal_connect_swapped( cbdata->core, "quit",
496                              G_CALLBACK( wannaquit ), cbdata );
497    g_signal_connect( cbdata->core, "prefs-changed",
498                      G_CALLBACK( prefschanged ), cbdata );
499
500    /* add torrents from command-line and saved state */
501    tr_core_load( cbdata->core, forcepause );
502    tr_core_add_list( cbdata->core, torrentFiles, start, prompt );
503    torrentFiles = NULL;
504    tr_core_torrents_added( cbdata->core );
505
506    /* set up main window */
507    winsetup( cbdata, wind );
508
509    /* set up the icon */
510    prefschanged( cbdata->core, PREF_KEY_SHOW_TRAY_ICON, cbdata );
511
512    /* start model update timer */
513    cbdata->timer = gtr_timeout_add_seconds( REFRESH_INTERVAL_SECONDS, updatemodel, cbdata );
514    updatemodel( cbdata );
515
516    /* either show the window or iconify it */
517    if( !isIconified )
518        gtk_widget_show( GTK_WIDGET( wind ) );
519    else
520    {
521        gtk_window_iconify( wind );
522        gtk_window_set_skip_taskbar_hint( cbdata->wind,
523                                          cbdata->icon != NULL );
524    }
525}
526
527static void
528tr_window_present( GtkWindow * window )
529{
530#if GTK_CHECK_VERSION( 2, 8, 0 )
531    gtk_window_present_with_time( window, gtk_get_current_event_time( ) );
532#else
533    gtk_window_present( window );
534#endif
535}
536
537static void
538toggleMainWindow( struct cbdata * cbdata,
539                  gboolean        doPresent )
540{
541    GtkWindow * window = GTK_WINDOW( cbdata->wind );
542    const int   doShow = cbdata->isIconified;
543    static int  x = 0;
544    static int  y = 0;
545
546    if( doShow || doPresent )
547    {
548        cbdata->isIconified = 0;
549        gtk_window_set_skip_taskbar_hint( window, FALSE );
550        gtk_window_move( window, x, y );
551        gtk_widget_show( GTK_WIDGET( window ) );
552        tr_window_present( window );
553    }
554    else
555    {
556        gtk_window_get_position( window, &x, &y );
557        gtk_window_set_skip_taskbar_hint( window, TRUE );
558        gtk_widget_hide( GTK_WIDGET( window ) );
559        cbdata->isIconified = 1;
560    }
561}
562
563static gboolean
564winclose( GtkWidget * w    UNUSED,
565          GdkEvent * event UNUSED,
566          gpointer         gdata )
567{
568    struct cbdata * cbdata = gdata;
569
570    if( cbdata->icon != NULL )
571        action_activate ( "toggle-main-window" );
572    else
573        askquit( cbdata->core, cbdata->wind, wannaquit, cbdata );
574
575    return TRUE; /* don't propagate event further */
576}
577
578static void
579rowChangedCB( GtkTreeModel  * model UNUSED,
580              GtkTreePath   * path,
581              GtkTreeIter   * iter  UNUSED,
582              gpointer        gdata )
583{
584    struct cbdata * data = gdata;
585    if( gtk_tree_selection_path_is_selected ( data->sel, path ) )
586        refreshTorrentActions( gdata );
587}
588
589static void
590winsetup( struct cbdata * cbdata,
591          TrWindow *      wind )
592{
593    GtkTreeModel *     model;
594    GtkTreeSelection * sel;
595
596    g_assert( NULL == cbdata->wind );
597    cbdata->wind = GTK_WINDOW( wind );
598    cbdata->sel = sel = GTK_TREE_SELECTION( tr_window_get_selection( cbdata->wind ) );
599
600    g_signal_connect( sel, "changed", G_CALLBACK( selectionChangedCB ), cbdata );
601    selectionChangedCB( sel, cbdata );
602    model = tr_core_model( cbdata->core );
603    g_signal_connect( model, "row-changed", G_CALLBACK( rowChangedCB ), cbdata );
604    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
605    refreshTorrentActions( cbdata );
606
607    setupdrag( GTK_WIDGET( wind ), cbdata );
608}
609
610static gpointer
611quitThreadFunc( gpointer gdata )
612{
613    struct cbdata * cbdata = gdata;
614
615    tr_core_close( cbdata->core );
616
617    /* shutdown the gui */
618    if( cbdata->prefs )
619        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
620    if( cbdata->wind )
621        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
622    g_object_unref( cbdata->core );
623    if( cbdata->icon )
624        g_object_unref( cbdata->icon );
625    if( cbdata->errqueue )
626    {
627        g_slist_foreach( cbdata->errqueue, (GFunc)g_free, NULL );
628        g_slist_free( cbdata->errqueue );
629    }
630    if( cbdata->dupqueue )
631    {
632        g_slist_foreach( cbdata->dupqueue, (GFunc)g_free, NULL );
633        g_slist_free( cbdata->dupqueue );
634    }
635
636    g_hash_table_destroy( cbdata->details2tor );
637    g_hash_table_destroy( cbdata->tor2details );
638    g_free( cbdata );
639
640    /* exit the gtk main loop */
641    gtk_main_quit( );
642    return NULL;
643}
644
645static void
646do_exit_cb( GtkWidget *w  UNUSED,
647            gpointer data UNUSED )
648{
649    exit( 0 );
650}
651
652static void
653wannaquit( void * vdata )
654{
655    GtkWidget *     r, * p, * b, * w, *c;
656    struct cbdata * cbdata = vdata;
657
658    /* stop the update timer */
659    if( cbdata->timer )
660    {
661        g_source_remove( cbdata->timer );
662        cbdata->timer = 0;
663    }
664
665    c = GTK_WIDGET( cbdata->wind );
666    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
667
668    r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
669    gtk_container_add( GTK_CONTAINER( c ), r );
670
671    p = gtk_table_new( 3, 2, FALSE );
672    gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
673    gtk_container_add( GTK_CONTAINER( r ), p );
674
675    w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
676    gtk_table_attach_defaults( GTK_TABLE( p ), w, 0, 1, 0, 2 );
677
678    w = gtk_label_new( NULL );
679    gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
680    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
681    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
682
683    w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
684    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
685    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
686
687    b = gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
688    w = gtr_button_new_from_stock( GTK_STOCK_QUIT, _( "_Quit Now" ) );
689    g_signal_connect( w, "clicked", G_CALLBACK( do_exit_cb ), NULL );
690    gtk_container_add( GTK_CONTAINER( b ), w );
691    gtk_table_attach( GTK_TABLE(
692                          p ), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
693
694    gtk_widget_show_all( r );
695
696    /* clear the UI */
697    gtk_list_store_clear( GTK_LIST_STORE( tr_core_model( cbdata->core ) ) );
698
699    /* shut down libT */
700    g_thread_create( quitThreadFunc, vdata, TRUE, NULL );
701}
702
703static void
704gotdrag( GtkWidget         * widget UNUSED,
705         GdkDragContext *           dc,
706         gint                x      UNUSED,
707         gint                y      UNUSED,
708         GtkSelectionData *         sel,
709         guint               info   UNUSED,
710         guint                      time,
711         gpointer                   gdata )
712{
713    struct cbdata * data = gdata;
714    GSList *        paths = NULL;
715    GSList *        freeme = NULL;
716
717#if 0
718    int             i;
719    char *          sele = gdk_atom_name( sel->selection );
720    char *          targ = gdk_atom_name( sel->target );
721    char *          type = gdk_atom_name( sel->type );
722
723    g_message( "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i",
724               sele, targ, type, sel->format, sel->length );
725    g_free( sele );
726    g_free( targ );
727    g_free( type );
728    if( sel->format == 8 )
729    {
730        for( i = 0; i < sel->length; ++i )
731            fprintf( stderr, "%02X ", sel->data[i] );
732        fprintf( stderr, "\n" );
733    }
734#endif
735
736    if( ( sel->format == 8 )
737      && ( sel->selection == gdk_atom_intern( "XdndSelection", FALSE ) ) )
738    {
739        int      i;
740        char *   str = g_strndup( (char*)sel->data, sel->length );
741        gchar ** files = g_strsplit_set( str, "\r\n", -1 );
742        for( i = 0; files && files[i]; ++i )
743        {
744            char * filename;
745            if( !*files[i] ) /* empty filename... */
746                continue;
747
748            /* decode the filename */
749            filename = decode_uri( files[i] );
750            freeme = g_slist_prepend( freeme, filename );
751            if( !g_utf8_validate( filename, -1, NULL ) )
752                continue;
753
754            /* walk past "file://", if present */
755            if( g_str_has_prefix( filename, "file:" ) )
756            {
757                filename += 5;
758                while( g_str_has_prefix( filename, "//" ) )
759                    ++filename;
760            }
761
762            /* if the file doesn't exist, the first part
763               might be a hostname ... walk past it. */
764            if( !g_file_test( filename, G_FILE_TEST_EXISTS ) )
765            {
766                char * pch = strchr( filename + 1, '/' );
767                if( pch != NULL )
768                    filename = pch;
769            }
770
771            /* finally, add it to the list of torrents to try adding */
772            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
773                paths = g_slist_prepend( paths, g_strdup( filename ) );
774        }
775
776        /* try to add any torrents we found */
777        if( paths )
778        {
779            paths = g_slist_reverse( paths );
780            tr_core_add_list_defaults( data->core, paths );
781            tr_core_torrents_added( data->core );
782        }
783
784        freestrlist( freeme );
785        g_strfreev( files );
786        g_free( str );
787    }
788
789    gtk_drag_finish( dc, ( NULL != paths ), FALSE, time );
790}
791
792static void
793setupdrag( GtkWidget *    widget,
794           struct cbdata *data )
795{
796    GtkTargetEntry targets[] = {
797        { (char*)"STRING",          0, 0 },
798        { (char*)"text/plain",      0, 0 },
799        { (char*)"text/uri-list",   0, 0 },
800    };
801
802    g_signal_connect( widget, "drag_data_received", G_CALLBACK(
803                          gotdrag ), data );
804
805    gtk_drag_dest_set( widget, GTK_DEST_DEFAULT_ALL, targets,
806                       ALEN( targets ), GDK_ACTION_COPY | GDK_ACTION_MOVE );
807}
808
809static void
810flushAddTorrentErrors( GtkWindow *  window,
811                       const char * primary,
812                       GSList **    files )
813{
814    GString *   s = g_string_new( NULL );
815    GSList *    l;
816    GtkWidget * w;
817
818    if( g_slist_length( *files ) > 1 ) {
819        for( l=*files; l!=NULL; l=l->next )
820            g_string_append_printf( s, "\xE2\x88\x99 %s\n", (const char*)l->data );
821    } else {
822        for( l=*files; l!=NULL; l=l->next )
823            g_string_append_printf( s, "%s\n", (const char*)l->data );
824    }
825    w = gtk_message_dialog_new( window,
826                                GTK_DIALOG_DESTROY_WITH_PARENT,
827                                GTK_MESSAGE_ERROR,
828                                GTK_BUTTONS_CLOSE,
829                                "%s", primary );
830    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
831                                              "%s", s->str );
832    g_signal_connect_swapped( w, "response",
833                              G_CALLBACK( gtk_widget_destroy ), w );
834    gtk_widget_show_all( w );
835    g_string_free( s, TRUE );
836
837    g_slist_foreach( *files, (GFunc)g_free, NULL );
838    g_slist_free( *files );
839    *files = NULL;
840}
841
842static void
843showTorrentErrors( struct cbdata * cbdata )
844{
845    if( cbdata->errqueue )
846        flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
847                               ngettext( "Couldn't add corrupt torrent",
848                                         "Couldn't add corrupt torrents",
849                                         g_slist_length( cbdata->errqueue ) ),
850                               &cbdata->errqueue );
851
852    if( cbdata->dupqueue )
853        flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
854                               ngettext( "Couldn't add duplicate torrent",
855                                         "Couldn't add duplicate torrents",
856                                         g_slist_length( cbdata->dupqueue ) ),
857                               &cbdata->dupqueue );
858}
859
860static void
861coreerr( TrCore * core    UNUSED,
862         enum tr_core_err code,
863         const char *     msg,
864         gpointer         gdata )
865{
866    struct cbdata * c = gdata;
867
868    switch( code )
869    {
870        case TR_EINVALID:
871            c->errqueue =
872                g_slist_append( c->errqueue, g_path_get_basename( msg ) );
873            break;
874
875        case TR_EDUPLICATE:
876            c->dupqueue = g_slist_append( c->dupqueue, g_strdup( msg ) );
877            break;
878
879        case TR_CORE_ERR_NO_MORE_TORRENTS:
880            showTorrentErrors( c );
881            break;
882
883        case TR_CORE_ERR_SAVE_STATE:
884            errmsg( c->wind, "%s", msg );
885            break;
886
887        default:
888            g_assert_not_reached( );
889            break;
890    }
891}
892
893#if GTK_CHECK_VERSION( 2, 8, 0 )
894static void
895on_main_window_focus_in( GtkWidget      * widget UNUSED,
896                         GdkEventFocus  * event  UNUSED,
897                         gpointer                gdata )
898{
899    struct cbdata * cbdata = gdata;
900
901    gtk_window_set_urgency_hint( GTK_WINDOW( cbdata->wind ), FALSE );
902}
903
904#endif
905
906static void
907onAddTorrent( TrCore *  core,
908              tr_ctor * ctor,
909              gpointer  gdata )
910{
911    struct cbdata * cbdata = gdata;
912    GtkWidget *     w = addSingleTorrentDialog( cbdata->wind, core, ctor );
913
914#if GTK_CHECK_VERSION( 2, 8, 0 )
915    g_signal_connect( w, "focus-in-event",
916                      G_CALLBACK( on_main_window_focus_in ),  cbdata );
917    gtk_window_set_urgency_hint( cbdata->wind, TRUE );
918#endif
919}
920
921static void
922prefschanged( TrCore * core UNUSED,
923              const char *  key,
924              gpointer      data )
925{
926    struct cbdata  * cbdata = data;
927    tr_session     * tr     = tr_core_session( cbdata->core );
928
929    if( !strcmp( key, TR_PREFS_KEY_ENCRYPTION ) )
930    {
931        const int encryption = pref_int_get( key );
932        g_message( "setting encryption to %d", encryption );
933        tr_sessionSetEncryption( tr, encryption );
934    }
935    else if( !strcmp( key, TR_PREFS_KEY_DOWNLOAD_DIR ) )
936    {
937        tr_sessionSetDownloadDir( tr, pref_string_get( key ) );
938    }
939    else if( !strcmp( key, TR_PREFS_KEY_MSGLEVEL ) )
940    {
941        tr_setMessageLevel( pref_int_get( key ) );
942    }
943    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
944    {
945        tr_sessionSetPeerPort( tr, pref_int_get( key ) );
946    }
947    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_ENABLED ) )
948    {
949        tr_blocklistSetEnabled( tr, pref_flag_get( key ) );
950    }
951    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
952    {
953        const int show = pref_flag_get( key );
954        if( show && !cbdata->icon )
955            cbdata->icon = tr_icon_new( cbdata->core );
956        else if( !show && cbdata->icon ) {
957            g_object_unref( cbdata->icon );
958            cbdata->icon = NULL;
959        }
960    }
961    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_ENABLED ) )
962    {
963        tr_sessionLimitSpeed( tr, TR_DOWN, pref_flag_get( key ) );
964    }
965    else if( !strcmp( key, TR_PREFS_KEY_DSPEED ) )
966    {
967        tr_sessionSetSpeedLimit( tr, TR_DOWN, pref_int_get( key ) );
968    }
969    else if( !strcmp( key, TR_PREFS_KEY_USPEED_ENABLED ) )
970    {
971        tr_sessionLimitSpeed( tr, TR_UP, pref_flag_get( key ) );
972    }
973    else if( !strcmp( key, TR_PREFS_KEY_USPEED ) )
974    {
975        tr_sessionSetSpeedLimit( tr, TR_UP, pref_int_get( key ) );
976    }
977    else if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) )
978    {
979        tr_sessionSetRatioLimited( tr, pref_flag_get( key ) );
980    }
981    else if( !strcmp( key, TR_PREFS_KEY_RATIO ) )
982    {
983        tr_sessionSetRatioLimit( tr, pref_double_get( key ) );
984    }
985    else if( !strcmp( key, TR_PREFS_KEY_PORT_FORWARDING ) )
986    {
987        tr_sessionSetPortForwardingEnabled( tr, pref_flag_get( key ) );
988    }
989    else if( !strcmp( key, TR_PREFS_KEY_PEX_ENABLED ) )
990    {
991        tr_sessionSetPexEnabled( tr, pref_flag_get( key ) );
992    }
993    else if( !strcmp( key, TR_PREFS_KEY_RPC_PORT ) )
994    {
995        tr_sessionSetRPCPort( tr, pref_int_get( key ) );
996    }
997    else if( !strcmp( key, TR_PREFS_KEY_RPC_ENABLED ) )
998    {
999        tr_sessionSetRPCEnabled( tr, pref_flag_get( key ) );
1000    }
1001    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST ) )
1002    {
1003        tr_sessionSetRPCWhitelist( tr, pref_string_get( key ) );
1004    }
1005    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED ) )
1006    {
1007        tr_sessionSetRPCWhitelistEnabled( tr, pref_flag_get( key ) );
1008    }
1009    else if( !strcmp( key, TR_PREFS_KEY_RPC_USERNAME ) )
1010    {
1011        tr_sessionSetRPCUsername( tr, pref_string_get( key ) );
1012    }
1013    else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
1014    {
1015        tr_sessionSetRPCPassword( tr, pref_string_get( key ) );
1016    }
1017    else if( !strcmp( key, TR_PREFS_KEY_RPC_AUTH_REQUIRED ) )
1018    {
1019        tr_sessionSetRPCPasswordEnabled( tr, pref_flag_get( key ) );
1020    }
1021    else if( !strcmp( key, TR_PREFS_KEY_PROXY ) )
1022    {
1023        tr_sessionSetProxy( tr, pref_string_get( key ) );
1024    }
1025    else if( !strcmp( key, TR_PREFS_KEY_PROXY_TYPE ) )
1026    {
1027        tr_sessionSetProxyType( tr, pref_int_get( key ) );
1028    }
1029    else if( !strcmp( key, TR_PREFS_KEY_PROXY_ENABLED ) )
1030    {
1031        tr_sessionSetProxyEnabled( tr, pref_flag_get( key ) );
1032    }
1033    else if( !strcmp( key, TR_PREFS_KEY_PROXY_AUTH_ENABLED ) )
1034    {
1035        tr_sessionSetProxyAuthEnabled( tr, pref_flag_get( key ) );
1036    }
1037    else if( !strcmp( key, TR_PREFS_KEY_PROXY_USERNAME ) )
1038    {
1039        tr_sessionSetProxyUsername( tr, pref_string_get( key ) );
1040    }
1041    else if( !strcmp( key, TR_PREFS_KEY_PROXY_PASSWORD ) )
1042    {
1043        tr_sessionSetProxyPassword( tr, pref_string_get( key ) );
1044    }
1045    else if( !strcmp( key, TR_PREFS_KEY_PROXY_PORT ) )
1046    {
1047        tr_sessionSetProxyPort( tr, pref_int_get( key ) );
1048    }
1049    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_UP ) )
1050    {
1051        tr_sessionSetAltSpeed( tr, TR_UP, pref_int_get( key ) );
1052    }
1053    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_DOWN ) )
1054    {
1055        tr_sessionSetAltSpeed( tr, TR_DOWN, pref_int_get( key ) );
1056    }
1057    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) )
1058    {
1059        tr_sessionUseAltSpeed( tr, pref_flag_get( key ) );
1060    }
1061    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN ) )
1062    {
1063        tr_sessionSetAltSpeedBegin( tr, pref_int_get( key ) );
1064    }
1065    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_END ) )
1066    {
1067        tr_sessionSetAltSpeedEnd( tr, pref_int_get( key ) );
1068    }
1069    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED ) )
1070    {
1071        tr_sessionUseAltSpeedTime( tr, pref_flag_get( key ) );
1072    }
1073    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_DAY ) )
1074    {
1075        tr_sessionSetAltSpeedDay( tr, pref_int_get( key ) );
1076    }
1077    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START ) )
1078    {
1079        tr_sessionSetPeerPortRandomOnStart( tr, pref_flag_get( key ) );
1080    }
1081}
1082
1083static gboolean
1084updatemodel( gpointer gdata )
1085{
1086    struct cbdata *data = gdata;
1087    const gboolean done = data->isClosing || global_sigcount;
1088
1089    if( !done )
1090    {
1091        /* update the torrent data in the model */
1092        tr_core_update( data->core );
1093
1094        /* update the main window's statusbar and toolbar buttons */
1095        if( data->wind )
1096            tr_window_update( data->wind );
1097
1098        /* update the actions */
1099        refreshTorrentActions( data );
1100    }
1101
1102    return !done;
1103}
1104
1105static void
1106aboutDialogActivateLink( GtkAboutDialog * dialog    UNUSED,
1107                         const gchar *              link_,
1108                         gpointer         user_data UNUSED )
1109{
1110    gtr_open_file( link_ );
1111}
1112
1113static void
1114about( GtkWindow * parent )
1115{
1116    const char *authors[] =
1117    {
1118        "Charles Kerr (Backend; GTK+)",
1119        "Mitchell Livingston (Backend; OS X)",
1120        "Eric Petit (Backend; OS X)",
1121        "Josh Elsasser (Daemon; Backend; GTK+)",
1122        "Bryan Varner (BeOS)",
1123        NULL
1124    };
1125
1126    const char *website_url = "http://www.transmissionbt.com/";
1127
1128    gtk_about_dialog_set_url_hook( aboutDialogActivateLink, NULL, NULL );
1129
1130    gtk_show_about_dialog( parent,
1131                           "name", g_get_application_name( ),
1132                           "comments",
1133                           _( "A fast and easy BitTorrent client" ),
1134                           "version", LONG_VERSION_STRING,
1135                           "website", website_url,
1136                           "website-label", website_url,
1137                           "copyright",
1138                           _( "Copyright 2005-2009 The Transmission Project" ),
1139                           "logo-icon-name", MY_NAME,
1140#ifdef SHOW_LICENSE
1141                           "license", LICENSE,
1142                           "wrap-license", TRUE,
1143#endif
1144                           "authors", authors,
1145                           /* Translators: translate "translator-credits" as
1146                              your name
1147                              to have it appear in the credits in the "About"
1148                              dialog */
1149                           "translator-credits", _( "translator-credits" ),
1150                           NULL );
1151}
1152
1153static void
1154startTorrentForeach( GtkTreeModel *      model,
1155                     GtkTreePath  * path UNUSED,
1156                     GtkTreeIter *       iter,
1157                     gpointer       data UNUSED )
1158{
1159    tr_torrent * tor = NULL;
1160
1161    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1162    tr_torrentStart( tor );
1163}
1164
1165static void
1166stopTorrentForeach( GtkTreeModel *      model,
1167                    GtkTreePath  * path UNUSED,
1168                    GtkTreeIter *       iter,
1169                    gpointer       data UNUSED )
1170{
1171    tr_torrent * tor = NULL;
1172
1173    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1174    tr_torrentStop( tor );
1175}
1176
1177static void
1178updateTrackerForeach( GtkTreeModel *      model,
1179                      GtkTreePath  * path UNUSED,
1180                      GtkTreeIter *       iter,
1181                      gpointer       data UNUSED )
1182{
1183    tr_torrent * tor = NULL;
1184
1185    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1186    tr_torrentManualUpdate( tor );
1187}
1188
1189static void
1190detailsClosed( gpointer  user_data,
1191               GObject * details )
1192{
1193    struct cbdata * data = user_data;
1194    gpointer        hashString = g_hash_table_lookup( data->details2tor,
1195                                                      details );
1196
1197    g_hash_table_remove( data->details2tor, details );
1198    g_hash_table_remove( data->tor2details, hashString );
1199}
1200
1201static void
1202openFolderForeach( GtkTreeModel *           model,
1203                   GtkTreePath  * path      UNUSED,
1204                   GtkTreeIter *            iter,
1205                   gpointer       user_data UNUSED )
1206{
1207    TrTorrent * gtor = NULL;
1208
1209    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1210    tr_torrent_open_folder( gtor );
1211    g_object_unref( G_OBJECT( gtor ) );
1212}
1213
1214static void
1215showInfoForeach( GtkTreeModel *      model,
1216                 GtkTreePath  * path UNUSED,
1217                 GtkTreeIter *       iter,
1218                 gpointer            user_data )
1219{
1220    const char *    hashString;
1221    struct cbdata * data = user_data;
1222    TrTorrent *     tor = NULL;
1223    GtkWidget *     w;
1224
1225    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
1226    hashString = tr_torrent_info( tor )->hashString;
1227    w = g_hash_table_lookup( data->tor2details, hashString );
1228    if( w != NULL )
1229        gtk_window_present( GTK_WINDOW( w ) );
1230    else
1231    {
1232        w = torrent_inspector_new( GTK_WINDOW( data->wind ), data->core, tor );
1233        gtk_widget_show( w );
1234        g_hash_table_insert( data->tor2details, (gpointer)hashString, w );
1235        g_hash_table_insert( data->details2tor, w, (gpointer)hashString );
1236        g_object_weak_ref( G_OBJECT( w ), detailsClosed, data );
1237    }
1238
1239    g_object_unref( G_OBJECT( tor ) );
1240}
1241
1242static void
1243recheckTorrentForeach( GtkTreeModel *      model,
1244                       GtkTreePath  * path UNUSED,
1245                       GtkTreeIter *       iter,
1246                       gpointer       data UNUSED )
1247{
1248    TrTorrent * gtor = NULL;
1249
1250    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1251    tr_torrentVerify( tr_torrent_handle( gtor ) );
1252    g_object_unref( G_OBJECT( gtor ) );
1253}
1254
1255static gboolean
1256msgwinclosed( void )
1257{
1258    action_toggle( "toggle-message-log", FALSE );
1259    return FALSE;
1260}
1261
1262static void
1263accumulateSelectedTorrents( GtkTreeModel *      model,
1264                            GtkTreePath  * path UNUSED,
1265                            GtkTreeIter *       iter,
1266                            gpointer            gdata )
1267{
1268    GSList **   data = ( GSList** ) gdata;
1269    TrTorrent * tor = NULL;
1270
1271    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
1272    *data = g_slist_prepend( *data, tor );
1273}
1274
1275static void
1276removeSelected( struct cbdata * data,
1277                gboolean        delete_files )
1278{
1279    GSList *           l = NULL;
1280    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1281
1282    gtk_tree_selection_selected_foreach( s, accumulateSelectedTorrents, &l );
1283    gtk_tree_selection_unselect_all( s );
1284    if( l )
1285    {
1286        l = g_slist_reverse( l );
1287        confirmRemove( data->wind, data->core, l, delete_files );
1288    }
1289}
1290
1291static void
1292startAllTorrents( struct cbdata * data )
1293{
1294    tr_session * session = tr_core_session( data->core );
1295    const char * cmd = "{ \"method\": \"torrent-start\" }";
1296    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1297}
1298
1299static void
1300pauseAllTorrents( struct cbdata * data )
1301{
1302    tr_session * session = tr_core_session( data->core );
1303    const char * cmd = "{ \"method\": \"torrent-stop\" }";
1304    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1305}
1306
1307void
1308doAction( const char * action_name, gpointer user_data )
1309{
1310    struct cbdata * data = user_data;
1311    gboolean        changed = FALSE;
1312
1313    if(  !strcmp( action_name, "add-torrent-menu" )
1314      || !strcmp( action_name, "add-torrent-toolbar" ) )
1315    {
1316        addDialog( data->wind, data->core );
1317    }
1318    else if( !strcmp( action_name, "show-stats" ) )
1319    {
1320        GtkWidget * dialog = stats_dialog_create( data->wind, data->core );
1321        gtk_widget_show( dialog );
1322    }
1323    else if( !strcmp( action_name, "start-torrent" ) )
1324    {
1325        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1326        gtk_tree_selection_selected_foreach( s, startTorrentForeach, NULL );
1327        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1328    }
1329    else if( !strcmp( action_name, "pause-all-torrents" ) )
1330    {
1331        pauseAllTorrents( data );
1332    }
1333    else if( !strcmp( action_name, "start-all-torrents" ) )
1334    {
1335        startAllTorrents( data );
1336    }
1337    else if( !strcmp( action_name, "pause-torrent" ) )
1338    {
1339        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1340        gtk_tree_selection_selected_foreach( s, stopTorrentForeach, NULL );
1341        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1342    }
1343    else if( !strcmp( action_name, "verify-torrent" ) )
1344    {
1345        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1346        gtk_tree_selection_selected_foreach( s, recheckTorrentForeach, NULL );
1347        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1348    }
1349    else if( !strcmp( action_name, "open-torrent-folder" ) )
1350    {
1351        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1352        gtk_tree_selection_selected_foreach( s, openFolderForeach, data );
1353    }
1354    else if( !strcmp( action_name, "show-torrent-properties" ) )
1355    {
1356        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1357        gtk_tree_selection_selected_foreach( s, showInfoForeach, data );
1358    }
1359    else if( !strcmp( action_name, "update-tracker" ) )
1360    {
1361        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1362        gtk_tree_selection_selected_foreach( s, updateTrackerForeach,
1363                                             data->wind );
1364    }
1365    else if( !strcmp( action_name, "new-torrent" ) )
1366    {
1367        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ),
1368                                     tr_core_session( data->core ) );
1369        gtk_widget_show_all( w );
1370    }
1371    else if( !strcmp( action_name, "remove-torrent" ) )
1372    {
1373        removeSelected( data, FALSE );
1374    }
1375    else if( !strcmp( action_name, "delete-torrent" ) )
1376    {
1377        removeSelected( data, TRUE );
1378    }
1379    else if( !strcmp( action_name, "quit" ) )
1380    {
1381        askquit( data->core, data->wind, wannaquit, data );
1382    }
1383    else if( !strcmp( action_name, "select-all" ) )
1384    {
1385        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1386        gtk_tree_selection_select_all( s );
1387    }
1388    else if( !strcmp( action_name, "deselect-all" ) )
1389    {
1390        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1391        gtk_tree_selection_unselect_all( s );
1392    }
1393    else if( !strcmp( action_name, "edit-preferences" ) )
1394    {
1395        if( NULL == data->prefs )
1396        {
1397            data->prefs = tr_prefs_dialog_new( G_OBJECT( data->core ),
1398                                               data->wind );
1399            g_signal_connect( data->prefs, "destroy",
1400                              G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1401            gtk_widget_show( GTK_WIDGET( data->prefs ) );
1402        }
1403    }
1404    else if( !strcmp( action_name, "toggle-message-log" ) )
1405    {
1406        if( !data->msgwin )
1407        {
1408            GtkWidget * win = msgwin_new( data->core );
1409            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ),
1410                              NULL );
1411            data->msgwin = win;
1412        }
1413        else
1414        {
1415            action_toggle( "toggle-message-log", FALSE );
1416            gtk_widget_destroy( data->msgwin );
1417            data->msgwin = NULL;
1418        }
1419    }
1420    else if( !strcmp( action_name, "show-about-dialog" ) )
1421    {
1422        about( data->wind );
1423    }
1424    else if( !strcmp ( action_name, "help" ) )
1425    {
1426        char * url = gtr_get_help_url( );
1427        gtr_open_file( url );
1428        g_free( url );
1429    }
1430    else if( !strcmp( action_name, "toggle-main-window" ) )
1431    {
1432        toggleMainWindow( data, FALSE );
1433    }
1434    else if( !strcmp( action_name, "present-main-window" ) )
1435    {
1436        toggleMainWindow( data, TRUE );
1437    }
1438    else g_error ( "Unhandled action: %s", action_name );
1439
1440    if( changed )
1441        updatemodel( data );
1442}
1443
Note: See TracBrowser for help on using the repository browser.