source: trunk/gtk/main.c @ 8034

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

(trunk gtk) minor tweaks

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