source: trunk/gtk/main.c @ 8846

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

remove some unused utilities. better commenting on the utils that remain.

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