source: trunk/gtk/main.c @ 9236

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

(trunk, gtk) #2478: revise the popup dialog text

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