source: trunk/gtk/main.c @ 9387

Last change on this file since 9387 was 9387, checked in by charles, 9 years ago

(trunk) trunk's just been too stable lately. #2119: reload settings.json on SIGHUP

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