source: trunk/gtk/main.c @ 7391

Last change on this file since 7391 was 7391, checked in by charles, 12 years ago

(trunk gtk) fix bug that didn't save the settings when the user changed the "blocklist enabled" setting. reported by Rol Col

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