source: trunk/gtk/main.c @ 9231

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

(trunk gtk) #2478: popup dialog for first-time users

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