source: trunk/gtk/main.c @ 9328

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

(trunk) #1483: move completed torrents to a user-specified directory + #629: different file extension for incomplete files

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