source: trunk/gtk/main.c @ 8420

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

(trunk gtk) #2090: Speed limit switch accessibility

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