source: trunk/gtk/main.c @ 8736

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

(trunk) silence some minor gcc warnings

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