source: trunk/gtk/main.c @ 8766

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

(trunk gtk) #2257: When popping up the shutdown "sending totals to tracker.." prompt, we need a GDK lock

  • Property svn:keywords set to Date Rev Author Id
File size: 46.5 KB
Line 
1/******************************************************************************
2 * $Id: main.c 8766 2009-07-01 04:10:04Z 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( void * 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        gtk_widget_show( errmsg_full( NULL, (callbackfunc_t)gtk_main_quit,
475                                      NULL, "%s", err ) );
476        g_free( err );
477        gtk_main( );
478    }
479
480    return 0;
481}
482
483static void
484appsetup( TrWindow *      wind,
485          GSList *        torrentFiles,
486          struct cbdata * cbdata,
487          gboolean        forcepause,
488          gboolean        isIconified )
489{
490    const pref_flag_t start =
491        forcepause ? PREF_FLAG_FALSE : PREF_FLAG_DEFAULT;
492    const pref_flag_t prompt = PREF_FLAG_DEFAULT;
493
494    /* fill out cbdata */
495    cbdata->wind         = NULL;
496    cbdata->icon         = NULL;
497    cbdata->msgwin       = NULL;
498    cbdata->prefs        = NULL;
499    cbdata->timer        = 0;
500    cbdata->isClosing    = 0;
501    cbdata->errqueue     = NULL;
502    cbdata->dupqueue     = NULL;
503    cbdata->isIconified  = isIconified;
504
505    if( isIconified )
506        pref_flag_set( PREF_KEY_SHOW_TRAY_ICON, TRUE );
507
508    actions_set_core( cbdata->core );
509
510    /* set up core handlers */
511    g_signal_connect( cbdata->core, "error", G_CALLBACK( coreerr ), cbdata );
512    g_signal_connect( cbdata->core, "add-torrent-prompt",
513                      G_CALLBACK( onAddTorrent ), cbdata );
514    g_signal_connect_swapped( cbdata->core, "quit",
515                              G_CALLBACK( wannaquit ), cbdata );
516    g_signal_connect( cbdata->core, "prefs-changed",
517                      G_CALLBACK( prefschanged ), cbdata );
518
519    /* add torrents from command-line and saved state */
520    tr_core_load( cbdata->core, forcepause );
521    tr_core_add_list( cbdata->core, torrentFiles, start, prompt, TRUE );
522    torrentFiles = NULL;
523    tr_core_torrents_added( cbdata->core );
524
525    /* set up main window */
526    winsetup( cbdata, wind );
527
528    /* set up the icon */
529    prefschanged( cbdata->core, PREF_KEY_SHOW_TRAY_ICON, cbdata );
530
531    /* start model update timer */
532    cbdata->timer = gtr_timeout_add_seconds( REFRESH_INTERVAL_SECONDS, updatemodel, cbdata );
533    updatemodel( cbdata );
534
535    /* either show the window or iconify it */
536    if( !isIconified )
537        gtk_widget_show( GTK_WIDGET( wind ) );
538    else
539    {
540        gtk_window_iconify( wind );
541        gtk_window_set_skip_taskbar_hint( cbdata->wind,
542                                          cbdata->icon != NULL );
543    }
544}
545
546static void
547tr_window_present( GtkWindow * window )
548{
549#if GTK_CHECK_VERSION( 2, 8, 0 )
550    gtk_window_present_with_time( window, gtk_get_current_event_time( ) );
551#else
552    gtk_window_present( window );
553#endif
554}
555
556static void
557toggleMainWindow( struct cbdata * cbdata,
558                  gboolean        doPresent )
559{
560    GtkWindow * window = GTK_WINDOW( cbdata->wind );
561    const int   doShow = cbdata->isIconified;
562    static int  x = 0;
563    static int  y = 0;
564
565    if( doShow || doPresent )
566    {
567        cbdata->isIconified = 0;
568        gtk_window_set_skip_taskbar_hint( window, FALSE );
569        gtk_window_move( window, x, y );
570        gtk_widget_show( GTK_WIDGET( window ) );
571        tr_window_present( window );
572    }
573    else
574    {
575        gtk_window_get_position( window, &x, &y );
576        gtk_window_set_skip_taskbar_hint( window, TRUE );
577        gtk_widget_hide( GTK_WIDGET( window ) );
578        cbdata->isIconified = 1;
579    }
580}
581
582static gboolean
583winclose( GtkWidget * w    UNUSED,
584          GdkEvent * event UNUSED,
585          gpointer         gdata )
586{
587    struct cbdata * cbdata = gdata;
588
589    if( cbdata->icon != NULL )
590        action_activate ( "toggle-main-window" );
591    else
592        askquit( cbdata->core, cbdata->wind, wannaquit, cbdata );
593
594    return TRUE; /* don't propagate event further */
595}
596
597static void
598rowChangedCB( GtkTreeModel  * model UNUSED,
599              GtkTreePath   * path,
600              GtkTreeIter   * iter  UNUSED,
601              gpointer        gdata )
602{
603    struct cbdata * data = gdata;
604    if( gtk_tree_selection_path_is_selected ( data->sel, path ) )
605        refreshActions( gdata );
606}
607
608static void
609winsetup( struct cbdata * cbdata,
610          TrWindow *      wind )
611{
612    GtkTreeModel *     model;
613    GtkTreeSelection * sel;
614
615    g_assert( NULL == cbdata->wind );
616    cbdata->wind = GTK_WINDOW( wind );
617    cbdata->sel = sel = GTK_TREE_SELECTION( tr_window_get_selection( cbdata->wind ) );
618
619    g_signal_connect( sel, "changed", G_CALLBACK( selectionChangedCB ), cbdata );
620    selectionChangedCB( sel, cbdata );
621    model = tr_core_model( cbdata->core );
622    g_signal_connect( model, "row-changed", G_CALLBACK( rowChangedCB ), cbdata );
623    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
624    refreshActions( cbdata );
625
626    setupdrag( GTK_WIDGET( wind ), cbdata );
627}
628
629static gpointer
630quitThreadFunc( gpointer gdata )
631{
632    struct cbdata * cbdata = gdata;
633    gdk_threads_enter( );
634
635    tr_core_close( cbdata->core );
636
637    /* shutdown the gui */
638    if( cbdata->details ) {
639        g_slist_foreach( cbdata->details, (GFunc)gtk_widget_destroy, NULL );
640        g_slist_free( cbdata->details );
641    }
642    if( cbdata->prefs )
643        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
644    if( cbdata->wind )
645        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
646    g_object_unref( cbdata->core );
647    if( cbdata->icon )
648        g_object_unref( cbdata->icon );
649    if( cbdata->errqueue ) {
650        g_slist_foreach( cbdata->errqueue, (GFunc)g_free, NULL );
651        g_slist_free( cbdata->errqueue );
652    }
653    if( cbdata->dupqueue ) {
654        g_slist_foreach( cbdata->dupqueue, (GFunc)g_free, NULL );
655        g_slist_free( cbdata->dupqueue );
656    }
657    g_free( cbdata );
658
659    gtk_main_quit( );
660    gdk_threads_leave( );
661
662    return NULL;
663}
664
665static void
666do_exit_cb( GtkWidget *w  UNUSED,
667            gpointer data UNUSED )
668{
669    exit( 0 );
670}
671
672static void
673wannaquit( void * vdata )
674{
675    GtkWidget *r, *p, *b, *w, *c;
676    struct cbdata *cbdata = vdata;
677
678    /* stop the update timer */
679    if( cbdata->timer )
680    {
681        g_source_remove( cbdata->timer );
682        cbdata->timer = 0;
683    }
684
685    c = GTK_WIDGET( cbdata->wind );
686    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
687
688    r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
689    gtk_container_add( GTK_CONTAINER( c ), r );
690
691    p = gtk_table_new( 3, 2, FALSE );
692    gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
693    gtk_container_add( GTK_CONTAINER( r ), p );
694
695    w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
696    gtk_table_attach_defaults( GTK_TABLE( p ), w, 0, 1, 0, 2 );
697
698    w = gtk_label_new( NULL );
699    gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
700    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
701    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
702
703    w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
704    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
705    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
706
707    b = gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
708    w = gtr_button_new_from_stock( GTK_STOCK_QUIT, _( "_Quit Now" ) );
709    g_signal_connect( w, "clicked", G_CALLBACK( do_exit_cb ), NULL );
710    gtk_container_add( GTK_CONTAINER( b ), w );
711    gtk_table_attach( GTK_TABLE( p ), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
712
713    gtk_widget_show_all( r );
714    gtk_widget_grab_focus( w );
715
716    /* clear the UI */
717    gtk_list_store_clear( GTK_LIST_STORE( tr_core_model( cbdata->core ) ) );
718
719    /* ensure the window is in its previous position & size.
720     * this seems to be necessary because changing the main window's
721     * child seems to unset the size */
722    gtk_window_resize( cbdata->wind, pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH ),
723                                     pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT ) );
724    gtk_window_move( cbdata->wind, pref_int_get( PREF_KEY_MAIN_WINDOW_X ),
725                                   pref_int_get( PREF_KEY_MAIN_WINDOW_Y ) );
726
727    /* shut down libT */
728    g_thread_create( quitThreadFunc, vdata, TRUE, NULL );
729}
730
731static void
732gotdrag( GtkWidget         * widget UNUSED,
733         GdkDragContext *           dc,
734         gint                x      UNUSED,
735         gint                y      UNUSED,
736         GtkSelectionData *         sel,
737         guint               info   UNUSED,
738         guint                      time,
739         gpointer                   gdata )
740{
741    struct cbdata * data = gdata;
742    GSList *        paths = NULL;
743    GSList *        freeme = NULL;
744
745#if 0
746    int             i;
747    char *          sele = gdk_atom_name( sel->selection );
748    char *          targ = gdk_atom_name( sel->target );
749    char *          type = gdk_atom_name( sel->type );
750
751    g_message( "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i",
752               sele, targ, type, sel->format, sel->length );
753    g_free( sele );
754    g_free( targ );
755    g_free( type );
756    if( sel->format == 8 )
757    {
758        for( i = 0; i < sel->length; ++i )
759            fprintf( stderr, "%02X ", sel->data[i] );
760        fprintf( stderr, "\n" );
761    }
762#endif
763
764    if( ( sel->format == 8 )
765      && ( sel->selection == gdk_atom_intern( "XdndSelection", FALSE ) ) )
766    {
767        int      i;
768        char *   str = g_strndup( (char*)sel->data, sel->length );
769        gchar ** files = g_strsplit_set( str, "\r\n", -1 );
770        for( i = 0; files && files[i]; ++i )
771        {
772            char * filename;
773            if( !*files[i] ) /* empty filename... */
774                continue;
775
776            /* decode the filename */
777            filename = decode_uri( files[i] );
778            freeme = g_slist_prepend( freeme, filename );
779            if( !g_utf8_validate( filename, -1, NULL ) )
780                continue;
781
782            /* walk past "file://", if present */
783            if( g_str_has_prefix( filename, "file:" ) )
784            {
785                filename += 5;
786                while( g_str_has_prefix( filename, "//" ) )
787                    ++filename;
788            }
789
790            /* if the file doesn't exist, the first part
791               might be a hostname ... walk past it. */
792            if( !g_file_test( filename, G_FILE_TEST_EXISTS ) )
793            {
794                char * pch = strchr( filename + 1, '/' );
795                if( pch != NULL )
796                    filename = pch;
797            }
798
799            /* finally, add it to the list of torrents to try adding */
800            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
801                paths = g_slist_prepend( paths, g_strdup( filename ) );
802        }
803
804        /* try to add any torrents we found */
805        if( paths )
806        {
807            paths = g_slist_reverse( paths );
808            tr_core_add_list_defaults( data->core, paths, TRUE );
809            tr_core_torrents_added( data->core );
810        }
811
812        freestrlist( freeme );
813        g_strfreev( files );
814        g_free( str );
815    }
816
817    gtk_drag_finish( dc, ( NULL != paths ), FALSE, time );
818}
819
820static void
821setupdrag( GtkWidget *    widget,
822           struct cbdata *data )
823{
824    GtkTargetEntry targets[] = {
825        { (char*)"STRING",          0, 0 },
826        { (char*)"text/plain",      0, 0 },
827        { (char*)"text/uri-list",   0, 0 },
828    };
829
830    g_signal_connect( widget, "drag_data_received", G_CALLBACK(
831                          gotdrag ), data );
832
833    gtk_drag_dest_set( widget, GTK_DEST_DEFAULT_ALL, targets,
834                       ALEN( targets ), GDK_ACTION_COPY | GDK_ACTION_MOVE );
835}
836
837static void
838flushAddTorrentErrors( GtkWindow *  window,
839                       const char * primary,
840                       GSList **    files )
841{
842    GString *   s = g_string_new( NULL );
843    GSList *    l;
844    GtkWidget * w;
845
846    if( g_slist_length( *files ) > 1 ) {
847        for( l=*files; l!=NULL; l=l->next )
848            g_string_append_printf( s, "\xE2\x88\x99 %s\n", (const char*)l->data );
849    } else {
850        for( l=*files; l!=NULL; l=l->next )
851            g_string_append_printf( s, "%s\n", (const char*)l->data );
852    }
853    w = gtk_message_dialog_new( window,
854                                GTK_DIALOG_DESTROY_WITH_PARENT,
855                                GTK_MESSAGE_ERROR,
856                                GTK_BUTTONS_CLOSE,
857                                "%s", primary );
858    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
859                                              "%s", s->str );
860    g_signal_connect_swapped( w, "response",
861                              G_CALLBACK( gtk_widget_destroy ), w );
862    gtk_widget_show_all( w );
863    g_string_free( s, TRUE );
864
865    g_slist_foreach( *files, (GFunc)g_free, NULL );
866    g_slist_free( *files );
867    *files = NULL;
868}
869
870static void
871showTorrentErrors( struct cbdata * cbdata )
872{
873    if( cbdata->errqueue )
874        flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
875                               ngettext( "Couldn't add corrupt torrent",
876                                         "Couldn't add corrupt torrents",
877                                         g_slist_length( cbdata->errqueue ) ),
878                               &cbdata->errqueue );
879
880    if( cbdata->dupqueue )
881        flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
882                               ngettext( "Couldn't add duplicate torrent",
883                                         "Couldn't add duplicate torrents",
884                                         g_slist_length( cbdata->dupqueue ) ),
885                               &cbdata->dupqueue );
886}
887
888static void
889coreerr( TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c )
890{
891    switch( code )
892    {
893        case TR_EINVALID:
894            c->errqueue =
895                g_slist_append( c->errqueue, g_path_get_basename( msg ) );
896            break;
897
898        case TR_EDUPLICATE:
899            c->dupqueue = g_slist_append( c->dupqueue, g_strdup( msg ) );
900            break;
901
902        case TR_CORE_ERR_NO_MORE_TORRENTS:
903            showTorrentErrors( c );
904            break;
905
906        case TR_CORE_ERR_SAVE_STATE:
907            errmsg( c->wind, "%s", msg );
908            break;
909
910        default:
911            g_assert_not_reached( );
912            break;
913    }
914}
915
916#if GTK_CHECK_VERSION( 2, 8, 0 )
917static void
918on_main_window_focus_in( GtkWidget      * widget UNUSED,
919                         GdkEventFocus  * event  UNUSED,
920                         gpointer                gdata )
921{
922    struct cbdata * cbdata = gdata;
923
924    if( cbdata->wind )
925        gtk_window_set_urgency_hint( GTK_WINDOW( cbdata->wind ), FALSE );
926}
927
928#endif
929
930static void
931onAddTorrent( TrCore *  core,
932              tr_ctor * ctor,
933              gpointer  gdata )
934{
935    struct cbdata * cbdata = gdata;
936    GtkWidget *     w = addSingleTorrentDialog( cbdata->wind, core, ctor );
937
938#if GTK_CHECK_VERSION( 2, 8, 0 )
939    g_signal_connect( w, "focus-in-event",
940                      G_CALLBACK( on_main_window_focus_in ),  cbdata );
941    if( cbdata->wind )
942        gtk_window_set_urgency_hint( cbdata->wind, TRUE );
943#endif
944}
945
946static void
947prefschanged( TrCore * core UNUSED,
948              const char *  key,
949              gpointer      data )
950{
951    struct cbdata  * cbdata = data;
952    tr_session     * tr     = tr_core_session( cbdata->core );
953
954    if( !strcmp( key, TR_PREFS_KEY_ENCRYPTION ) )
955    {
956        tr_sessionSetEncryption( tr, pref_int_get( key ) );
957    }
958    else if( !strcmp( key, TR_PREFS_KEY_DOWNLOAD_DIR ) )
959    {
960        tr_sessionSetDownloadDir( tr, pref_string_get( key ) );
961    }
962    else if( !strcmp( key, TR_PREFS_KEY_MSGLEVEL ) )
963    {
964        tr_setMessageLevel( pref_int_get( key ) );
965    }
966    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
967    {
968        tr_sessionSetPeerPort( tr, pref_int_get( key ) );
969    }
970    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_ENABLED ) )
971    {
972        tr_blocklistSetEnabled( tr, pref_flag_get( key ) );
973    }
974    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
975    {
976        const int show = pref_flag_get( key );
977        if( show && !cbdata->icon )
978            cbdata->icon = tr_icon_new( cbdata->core );
979        else if( !show && cbdata->icon ) {
980            g_object_unref( cbdata->icon );
981            cbdata->icon = NULL;
982        }
983    }
984    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_ENABLED ) )
985    {
986        tr_sessionLimitSpeed( tr, TR_DOWN, pref_flag_get( key ) );
987    }
988    else if( !strcmp( key, TR_PREFS_KEY_DSPEED ) )
989    {
990        tr_sessionSetSpeedLimit( tr, TR_DOWN, pref_int_get( key ) );
991    }
992    else if( !strcmp( key, TR_PREFS_KEY_USPEED_ENABLED ) )
993    {
994        tr_sessionLimitSpeed( tr, TR_UP, pref_flag_get( key ) );
995    }
996    else if( !strcmp( key, TR_PREFS_KEY_USPEED ) )
997    {
998        tr_sessionSetSpeedLimit( tr, TR_UP, pref_int_get( key ) );
999    }
1000    else if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) )
1001    {
1002        tr_sessionSetRatioLimited( tr, pref_flag_get( key ) );
1003    }
1004    else if( !strcmp( key, TR_PREFS_KEY_RATIO ) )
1005    {
1006        tr_sessionSetRatioLimit( tr, pref_double_get( key ) );
1007    }
1008    else if( !strcmp( key, TR_PREFS_KEY_PORT_FORWARDING ) )
1009    {
1010        tr_sessionSetPortForwardingEnabled( tr, pref_flag_get( key ) );
1011    }
1012    else if( !strcmp( key, TR_PREFS_KEY_PEX_ENABLED ) )
1013    {
1014        tr_sessionSetPexEnabled( tr, pref_flag_get( key ) );
1015    }
1016    else if( !strcmp( key, TR_PREFS_KEY_DHT_ENABLED ) )
1017    {
1018        tr_sessionSetDHTEnabled( tr, pref_flag_get( key ) );
1019    }
1020    else if( !strcmp( key, TR_PREFS_KEY_RPC_PORT ) )
1021    {
1022        tr_sessionSetRPCPort( tr, pref_int_get( key ) );
1023    }
1024    else if( !strcmp( key, TR_PREFS_KEY_RPC_ENABLED ) )
1025    {
1026        tr_sessionSetRPCEnabled( tr, pref_flag_get( key ) );
1027    }
1028    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST ) )
1029    {
1030        tr_sessionSetRPCWhitelist( tr, pref_string_get( key ) );
1031    }
1032    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED ) )
1033    {
1034        tr_sessionSetRPCWhitelistEnabled( tr, pref_flag_get( key ) );
1035    }
1036    else if( !strcmp( key, TR_PREFS_KEY_RPC_USERNAME ) )
1037    {
1038        tr_sessionSetRPCUsername( tr, pref_string_get( key ) );
1039    }
1040    else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
1041    {
1042        tr_sessionSetRPCPassword( tr, pref_string_get( key ) );
1043    }
1044    else if( !strcmp( key, TR_PREFS_KEY_RPC_AUTH_REQUIRED ) )
1045    {
1046        tr_sessionSetRPCPasswordEnabled( tr, pref_flag_get( key ) );
1047    }
1048    else if( !strcmp( key, TR_PREFS_KEY_PROXY ) )
1049    {
1050        tr_sessionSetProxy( tr, pref_string_get( key ) );
1051    }
1052    else if( !strcmp( key, TR_PREFS_KEY_PROXY_TYPE ) )
1053    {
1054        tr_sessionSetProxyType( tr, pref_int_get( key ) );
1055    }
1056    else if( !strcmp( key, TR_PREFS_KEY_PROXY_ENABLED ) )
1057    {
1058        tr_sessionSetProxyEnabled( tr, pref_flag_get( key ) );
1059    }
1060    else if( !strcmp( key, TR_PREFS_KEY_PROXY_AUTH_ENABLED ) )
1061    {
1062        tr_sessionSetProxyAuthEnabled( tr, pref_flag_get( key ) );
1063    }
1064    else if( !strcmp( key, TR_PREFS_KEY_PROXY_USERNAME ) )
1065    {
1066        tr_sessionSetProxyUsername( tr, pref_string_get( key ) );
1067    }
1068    else if( !strcmp( key, TR_PREFS_KEY_PROXY_PASSWORD ) )
1069    {
1070        tr_sessionSetProxyPassword( tr, pref_string_get( key ) );
1071    }
1072    else if( !strcmp( key, TR_PREFS_KEY_PROXY_PORT ) )
1073    {
1074        tr_sessionSetProxyPort( tr, pref_int_get( key ) );
1075    }
1076    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_UP ) )
1077    {
1078        tr_sessionSetAltSpeed( tr, TR_UP, pref_int_get( key ) );
1079    }
1080    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_DOWN ) )
1081    {
1082        tr_sessionSetAltSpeed( tr, TR_DOWN, pref_int_get( key ) );
1083    }
1084    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) )
1085    {
1086        const gboolean b = pref_flag_get( key );
1087        tr_sessionUseAltSpeed( tr, b );
1088        action_toggle( key, b );
1089    }
1090    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN ) )
1091    {
1092        tr_sessionSetAltSpeedBegin( tr, pref_int_get( key ) );
1093    }
1094    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_END ) )
1095    {
1096        tr_sessionSetAltSpeedEnd( tr, pref_int_get( key ) );
1097    }
1098    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED ) )
1099    {
1100        tr_sessionUseAltSpeedTime( tr, pref_flag_get( key ) );
1101    }
1102    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_DAY ) )
1103    {
1104        tr_sessionSetAltSpeedDay( tr, pref_int_get( key ) );
1105    }
1106    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START ) )
1107    {
1108        tr_sessionSetPeerPortRandomOnStart( tr, pref_flag_get( key ) );
1109    }
1110}
1111
1112static gboolean
1113updatemodel( gpointer gdata )
1114{
1115    struct cbdata *data = gdata;
1116    const gboolean done = data->isClosing || global_sigcount;
1117
1118    if( !done )
1119    {
1120        /* update the torrent data in the model */
1121        tr_core_update( data->core );
1122
1123        /* update the main window's statusbar and toolbar buttons */
1124        if( data->wind )
1125            tr_window_update( data->wind );
1126
1127        /* update the actions */
1128        refreshActions( data );
1129    }
1130
1131    return !done;
1132}
1133
1134static void
1135aboutDialogActivateLink( GtkAboutDialog * dialog    UNUSED,
1136                         const gchar *              link_,
1137                         gpointer         user_data UNUSED )
1138{
1139    gtr_open_file( link_ );
1140}
1141
1142static void
1143about( GtkWindow * parent )
1144{
1145    const char *authors[] =
1146    {
1147        "Charles Kerr (Backend; GTK+)",
1148        "Mitchell Livingston (Backend; OS X)",
1149        "Kevin Glowacz (Web client)",
1150        NULL
1151    };
1152
1153    const char *website_url = "http://www.transmissionbt.com/";
1154
1155    gtk_about_dialog_set_url_hook( aboutDialogActivateLink, NULL, NULL );
1156
1157    gtk_show_about_dialog( parent,
1158                           "name", g_get_application_name( ),
1159                           "comments",
1160                           _( "A fast and easy BitTorrent client" ),
1161                           "version", LONG_VERSION_STRING,
1162                           "website", website_url,
1163                           "website-label", website_url,
1164                           "copyright",
1165                           _( "Copyright 2005-2009 The Transmission Project" ),
1166                           "logo-icon-name", MY_NAME,
1167#ifdef SHOW_LICENSE
1168                           "license", LICENSE,
1169                           "wrap-license", TRUE,
1170#endif
1171                           "authors", authors,
1172                           /* Translators: translate "translator-credits" as
1173                              your name
1174                              to have it appear in the credits in the "About"
1175                              dialog */
1176                           "translator-credits", _( "translator-credits" ),
1177                           NULL );
1178}
1179
1180static void
1181startTorrentForeach( GtkTreeModel *      model,
1182                     GtkTreePath  * path UNUSED,
1183                     GtkTreeIter *       iter,
1184                     gpointer       data UNUSED )
1185{
1186    tr_torrent * tor = NULL;
1187
1188    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1189    tr_torrentStart( tor );
1190}
1191
1192static void
1193stopTorrentForeach( GtkTreeModel *      model,
1194                    GtkTreePath  * path UNUSED,
1195                    GtkTreeIter *       iter,
1196                    gpointer       data UNUSED )
1197{
1198    tr_torrent * tor = NULL;
1199
1200    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1201    tr_torrentStop( tor );
1202}
1203
1204static void
1205updateTrackerForeach( GtkTreeModel *      model,
1206                      GtkTreePath  * path UNUSED,
1207                      GtkTreeIter *       iter,
1208                      gpointer       data UNUSED )
1209{
1210    tr_torrent * tor = NULL;
1211
1212    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1213    tr_torrentManualUpdate( tor );
1214}
1215
1216static void
1217openFolderForeach( GtkTreeModel *           model,
1218                   GtkTreePath  * path      UNUSED,
1219                   GtkTreeIter *            iter,
1220                   gpointer       user_data UNUSED )
1221{
1222    TrTorrent * gtor = NULL;
1223
1224    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1225    tr_torrent_open_folder( gtor );
1226    g_object_unref( G_OBJECT( gtor ) );
1227}
1228
1229static void
1230recheckTorrentForeach( GtkTreeModel *      model,
1231                       GtkTreePath  * path UNUSED,
1232                       GtkTreeIter *       iter,
1233                       gpointer       data UNUSED )
1234{
1235    TrTorrent * gtor = NULL;
1236
1237    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1238    tr_torrentVerify( tr_torrent_handle( gtor ) );
1239    g_object_unref( G_OBJECT( gtor ) );
1240}
1241
1242static gboolean
1243msgwinclosed( void )
1244{
1245    action_toggle( "toggle-message-log", FALSE );
1246    return FALSE;
1247}
1248
1249static void
1250accumulateSelectedTorrents( GtkTreeModel *      model,
1251                            GtkTreePath  * path UNUSED,
1252                            GtkTreeIter *       iter,
1253                            gpointer            gdata )
1254{
1255    GSList **   data = ( GSList** ) gdata;
1256    TrTorrent * tor = NULL;
1257
1258    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
1259    *data = g_slist_prepend( *data, tor );
1260}
1261
1262static void
1263removeSelected( struct cbdata * data,
1264                gboolean        delete_files )
1265{
1266    GSList *           l = NULL;
1267    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1268
1269    gtk_tree_selection_selected_foreach( s, accumulateSelectedTorrents, &l );
1270    gtk_tree_selection_unselect_all( s );
1271    if( l )
1272    {
1273        l = g_slist_reverse( l );
1274        confirmRemove( data->wind, data->core, l, delete_files );
1275    }
1276}
1277
1278static void
1279startAllTorrents( struct cbdata * data )
1280{
1281    tr_session * session = tr_core_session( data->core );
1282    const char * cmd = "{ \"method\": \"torrent-start\" }";
1283    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1284}
1285
1286static void
1287pauseAllTorrents( struct cbdata * data )
1288{
1289    tr_session * session = tr_core_session( data->core );
1290    const char * cmd = "{ \"method\": \"torrent-stop\" }";
1291    tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1292}
1293
1294static tr_torrent*
1295getFirstSelectedTorrent( struct cbdata * data )
1296{
1297    tr_torrent * tor = NULL;
1298    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1299    GtkTreeModel * m;
1300    GList * l = gtk_tree_selection_get_selected_rows( s, &m );
1301    if( l != NULL ) {
1302        GtkTreePath * p = l->data;
1303        GtkTreeIter i;
1304        if( gtk_tree_model_get_iter( m, &i, p ) )
1305            gtk_tree_model_get( m, &i, MC_TORRENT_RAW, &tor, -1 );
1306    }
1307    g_list_foreach( l, (GFunc)gtk_tree_path_free, NULL );
1308    g_list_free( l );
1309    return tor;
1310}
1311
1312static void
1313detailsClosed( gpointer gdata, GObject * dead )
1314{
1315    struct cbdata * data = gdata;
1316    data->details = g_slist_remove( data->details, dead );
1317}
1318
1319void
1320doAction( const char * action_name, gpointer user_data )
1321{
1322    struct cbdata * data = user_data;
1323    gboolean        changed = FALSE;
1324
1325    if(  !strcmp( action_name, "add-torrent-menu" )
1326      || !strcmp( action_name, "add-torrent-toolbar" ) )
1327    {
1328        addDialog( data->wind, data->core );
1329    }
1330    else if( !strcmp( action_name, "show-stats" ) )
1331    {
1332        GtkWidget * dialog = stats_dialog_create( data->wind, data->core );
1333        gtk_widget_show( dialog );
1334    }
1335    else if( !strcmp( action_name, "start-torrent" ) )
1336    {
1337        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1338        gtk_tree_selection_selected_foreach( s, startTorrentForeach, NULL );
1339        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1340    }
1341    else if( !strcmp( action_name, "pause-all-torrents" ) )
1342    {
1343        pauseAllTorrents( data );
1344    }
1345    else if( !strcmp( action_name, "start-all-torrents" ) )
1346    {
1347        startAllTorrents( data );
1348    }
1349    else if( !strcmp( action_name, "relocate-torrent" ) )
1350    {
1351        tr_torrent * tor = getFirstSelectedTorrent( data );
1352        if( tor )
1353        {
1354            GtkWindow * parent = GTK_WINDOW( data->wind );
1355            GtkWidget * w = gtr_relocate_dialog_new( parent, tor );
1356            gtk_widget_show( w );
1357        }
1358    }
1359    else if( !strcmp( action_name, "pause-torrent" ) )
1360    {
1361        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1362        gtk_tree_selection_selected_foreach( s, stopTorrentForeach, NULL );
1363        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1364    }
1365    else if( !strcmp( action_name, "verify-torrent" ) )
1366    {
1367        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1368        gtk_tree_selection_selected_foreach( s, recheckTorrentForeach, NULL );
1369        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1370    }
1371    else if( !strcmp( action_name, "open-torrent-folder" ) )
1372    {
1373        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1374        gtk_tree_selection_selected_foreach( s, openFolderForeach, data );
1375    }
1376    else if( !strcmp( action_name, "show-torrent-properties" ) )
1377    {
1378        GtkWidget * w = torrent_inspector_new( GTK_WINDOW( data->wind ), data->core );
1379        data->details = g_slist_prepend( data->details, w );
1380        g_object_weak_ref( G_OBJECT( w ), detailsClosed, data );
1381        refreshDetailsDialog( data, w );
1382        gtk_widget_show( w );
1383    }
1384    else if( !strcmp( action_name, "update-tracker" ) )
1385    {
1386        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1387        gtk_tree_selection_selected_foreach( s, updateTrackerForeach,
1388                                             data->wind );
1389    }
1390    else if( !strcmp( action_name, "new-torrent" ) )
1391    {
1392        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ),
1393                                     tr_core_session( data->core ) );
1394        gtk_widget_show_all( w );
1395    }
1396    else if( !strcmp( action_name, "remove-torrent" ) )
1397    {
1398        removeSelected( data, FALSE );
1399    }
1400    else if( !strcmp( action_name, "delete-torrent" ) )
1401    {
1402        removeSelected( data, TRUE );
1403    }
1404    else if( !strcmp( action_name, "quit" ) )
1405    {
1406        askquit( data->core, data->wind, wannaquit, data );
1407    }
1408    else if( !strcmp( action_name, "select-all" ) )
1409    {
1410        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1411        gtk_tree_selection_select_all( s );
1412    }
1413    else if( !strcmp( action_name, "deselect-all" ) )
1414    {
1415        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1416        gtk_tree_selection_unselect_all( s );
1417    }
1418    else if( !strcmp( action_name, "edit-preferences" ) )
1419    {
1420        if( NULL == data->prefs )
1421        {
1422            data->prefs = tr_prefs_dialog_new( G_OBJECT( data->core ),
1423                                               data->wind );
1424            g_signal_connect( data->prefs, "destroy",
1425                              G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1426            gtk_widget_show( GTK_WIDGET( data->prefs ) );
1427        }
1428    }
1429    else if( !strcmp( action_name, "toggle-message-log" ) )
1430    {
1431        if( !data->msgwin )
1432        {
1433            GtkWidget * win = msgwin_new( data->core );
1434            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ),
1435                              NULL );
1436            data->msgwin = win;
1437        }
1438        else
1439        {
1440            action_toggle( "toggle-message-log", FALSE );
1441            gtk_widget_destroy( data->msgwin );
1442            data->msgwin = NULL;
1443        }
1444    }
1445    else if( !strcmp( action_name, "show-about-dialog" ) )
1446    {
1447        about( data->wind );
1448    }
1449    else if( !strcmp ( action_name, "help" ) )
1450    {
1451        char * url = gtr_get_help_url( );
1452        gtr_open_file( url );
1453        g_free( url );
1454    }
1455    else if( !strcmp( action_name, "toggle-main-window" ) )
1456    {
1457        toggleMainWindow( data, FALSE );
1458    }
1459    else if( !strcmp( action_name, "present-main-window" ) )
1460    {
1461        toggleMainWindow( data, TRUE );
1462    }
1463    else g_error ( "Unhandled action: %s", action_name );
1464
1465    if( changed )
1466        updatemodel( data );
1467}
Note: See TracBrowser for help on using the repository browser.