source: branches/1.7x/gtk/main.c @ 9067

Last change on this file since 9067 was 9067, checked in by charles, 13 years ago

(1.7x gtk) #2387: transmission doesn't start; gives message 'err: Did not receive a reply'

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