source: trunk/gtk/main.c @ 7396

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

(trunk gtk) fix PEX option bug, again reported by Rolcol

  • Property svn:keywords set to Date Rev Author Id
File size: 47.8 KB
Line 
1/******************************************************************************
2 * $Id: main.c 7396 2008-12-14 23:46:57Z 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_DOWNLOAD_DIR ) )
1038    {
1039        tr_sessionSetDownloadDir( tr, pref_string_get( key ) );
1040    }
1041    else if( !strcmp( key, TR_PREFS_KEY_MSGLEVEL ) )
1042    {
1043        tr_setMessageLevel( pref_int_get( key ) );
1044    }
1045    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT_RANDOM_ENABLED ) )
1046    {
1047        /* FIXME */
1048    }
1049    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
1050    {
1051        const int port = pref_int_get( key );
1052        tr_sessionSetPeerPort( tr, port );
1053    }
1054    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_ENABLED ) )
1055    {
1056        const gboolean flag = pref_flag_get( key );
1057        tr_blocklistSetEnabled( tr, flag );
1058    }
1059    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
1060    {
1061        const int show = pref_flag_get( key );
1062        if( show && !cbdata->icon )
1063            cbdata->icon = tr_icon_new( cbdata->core );
1064        else if( !show && cbdata->icon )
1065        {
1066            g_object_unref( cbdata->icon );
1067            cbdata->icon = NULL;
1068        }
1069    }
1070    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_ENABLED ) )
1071    {
1072        const gboolean b = pref_flag_get( key );
1073        tr_sessionSetSpeedLimitEnabled( tr, TR_DOWN, b );
1074    }
1075    else if( !strcmp( key, TR_PREFS_KEY_DSPEED ) )
1076    {
1077        const int limit = pref_int_get( key );
1078        tr_sessionSetSpeedLimit( tr, TR_DOWN, limit );
1079    }
1080    else if( !strcmp( key, TR_PREFS_KEY_USPEED_ENABLED ) )
1081    {
1082        const gboolean b = pref_flag_get( key );
1083        tr_sessionSetSpeedLimitEnabled( tr, TR_UP, b );
1084    }
1085    else if( !strcmp( key, TR_PREFS_KEY_USPEED ) )
1086    {
1087        const int limit = pref_int_get( key );
1088        tr_sessionSetSpeedLimit( tr, TR_UP, limit );
1089    }
1090    else if( !strncmp( key, "sched-", 6 ) )
1091    {
1092        updateScheduledLimits( tr );
1093    }
1094    else if( !strcmp( key, TR_PREFS_KEY_PORT_FORWARDING ) )
1095    {
1096        tr_sessionSetPortForwardingEnabled( tr, pref_flag_get( key ) );
1097    }
1098    else if( !strcmp( key, TR_PREFS_KEY_PEX_ENABLED ) )
1099    {
1100        tr_sessionSetPexEnabled( tr, pref_flag_get( key ) );
1101    }
1102    else if( !strcmp( key, TR_PREFS_KEY_RPC_PORT ) )
1103    {
1104        tr_sessionSetRPCPort( tr, pref_int_get( key ) );
1105    }
1106    else if( !strcmp( key, TR_PREFS_KEY_RPC_ENABLED ) )
1107    {
1108        tr_sessionSetRPCEnabled( tr, pref_flag_get( key ) );
1109    }
1110    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST ) )
1111    {
1112        const char * s = pref_string_get( key );
1113        tr_sessionSetRPCWhitelist( tr, s );
1114    }
1115    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED ) )
1116    {
1117        tr_sessionSetRPCWhitelistEnabled( tr, pref_flag_get( key ) );
1118    }
1119    else if( !strcmp( key, TR_PREFS_KEY_RPC_USERNAME ) )
1120    {
1121        const char * s = pref_string_get( key );
1122        tr_sessionSetRPCUsername( tr, s );
1123    }
1124    else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
1125    {
1126        const char * s = pref_string_get( key );
1127        tr_sessionSetRPCPassword( tr, s );
1128    }
1129    else if( !strcmp( key, TR_PREFS_KEY_RPC_AUTH_REQUIRED ) )
1130    {
1131        const gboolean enabled = pref_flag_get( key );
1132        tr_sessionSetRPCPasswordEnabled( tr, enabled );
1133    }
1134    else if( !strcmp( key, TR_PREFS_KEY_PROXY ) )
1135    {
1136        const char * s = pref_string_get( key );
1137        tr_sessionSetProxy( tr, s );
1138    }
1139    else if( !strcmp( key, TR_PREFS_KEY_PROXY_TYPE ) )
1140    {
1141        const int i = pref_int_get( key );
1142        tr_sessionSetProxyType( tr, i );
1143    }
1144    else if( !strcmp( key, TR_PREFS_KEY_PROXY_ENABLED ) )
1145    {
1146        const gboolean enabled = pref_flag_get( key );
1147        tr_sessionSetProxyEnabled( tr, enabled );
1148    }
1149    else if( !strcmp( key, TR_PREFS_KEY_PROXY_AUTH_ENABLED ) )
1150    {
1151        const gboolean enabled = pref_flag_get( key );
1152        tr_sessionSetProxyAuthEnabled( tr, enabled );
1153    }
1154    else if( !strcmp( key, TR_PREFS_KEY_PROXY_USERNAME ) )
1155    {
1156        const char * s = pref_string_get( key );
1157        tr_sessionSetProxyUsername( tr, s );
1158    }
1159    else if( !strcmp( key, TR_PREFS_KEY_PROXY_PASSWORD ) )
1160    {
1161        const char * s = pref_string_get( key );
1162        tr_sessionSetProxyPassword( tr, s );
1163    }
1164    else if( !strcmp( key, TR_PREFS_KEY_PROXY_PORT ) )
1165    {
1166        tr_sessionSetProxyPort( tr, pref_int_get( key ) );
1167    }
1168    else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
1169    {
1170        const char * s = pref_string_get( key );
1171        tr_sessionSetProxyPassword( tr, s );
1172    }
1173}
1174
1175static gboolean
1176updatemodel( gpointer gdata )
1177{
1178    struct cbdata *data = gdata;
1179    const gboolean done = data->isClosing || global_sigcount;
1180
1181    if( !done )
1182    {
1183        /* update the torrent data in the model */
1184        tr_core_update( data->core );
1185
1186        /* update the main window's statusbar and toolbar buttons */
1187        if( data->wind )
1188            tr_window_update( data->wind );
1189
1190        /* update the actions */
1191        refreshTorrentActions( data );
1192    }
1193
1194    return !done;
1195}
1196
1197static void
1198aboutDialogActivateLink( GtkAboutDialog * dialog    UNUSED,
1199                         const gchar *              link_,
1200                         gpointer         user_data UNUSED )
1201{
1202    gtr_open_file( link_ );
1203}
1204
1205static void
1206about( GtkWindow * parent )
1207{
1208    const char *authors[] =
1209    {
1210        "Charles Kerr (Backend; GTK+)",
1211        "Mitchell Livingston (Backend; OS X)",
1212        "Eric Petit (Backend; OS X)",
1213        "Josh Elsasser (Daemon; Backend; GTK+)",
1214        "Bryan Varner (BeOS)",
1215        NULL
1216    };
1217
1218    const char *website_url = "http://www.transmissionbt.com/";
1219
1220    gtk_about_dialog_set_url_hook( aboutDialogActivateLink, NULL, NULL );
1221
1222    gtk_show_about_dialog( parent,
1223                           "name", g_get_application_name( ),
1224                           "comments",
1225                           _( "A fast and easy BitTorrent client" ),
1226                           "version", LONG_VERSION_STRING,
1227                           "website", website_url,
1228                           "website-label", website_url,
1229                           "copyright",
1230                           _( "Copyright 2005-2008 The Transmission Project" ),
1231                           "logo-icon-name", MY_NAME,
1232#ifdef SHOW_LICENSE
1233                           "license", LICENSE,
1234                           "wrap-license", TRUE,
1235#endif
1236                           "authors", authors,
1237                           /* Translators: translate "translator-credits" as
1238                              your name
1239                              to have it appear in the credits in the "About"
1240                              dialog */
1241                           "translator-credits", _( "translator-credits" ),
1242                           NULL );
1243}
1244
1245static void
1246startTorrentForeach( GtkTreeModel *      model,
1247                     GtkTreePath  * path UNUSED,
1248                     GtkTreeIter *       iter,
1249                     gpointer       data UNUSED )
1250{
1251    tr_torrent * tor = NULL;
1252
1253    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1254    tr_torrentStart( tor );
1255}
1256
1257static void
1258stopTorrentForeach( GtkTreeModel *      model,
1259                    GtkTreePath  * path UNUSED,
1260                    GtkTreeIter *       iter,
1261                    gpointer       data UNUSED )
1262{
1263    tr_torrent * tor = NULL;
1264
1265    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1266    tr_torrentStop( tor );
1267}
1268
1269static void
1270updateTrackerForeach( GtkTreeModel *      model,
1271                      GtkTreePath  * path UNUSED,
1272                      GtkTreeIter *       iter,
1273                      gpointer       data UNUSED )
1274{
1275    tr_torrent * tor = NULL;
1276
1277    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1278    tr_torrentManualUpdate( tor );
1279}
1280
1281static void
1282detailsClosed( gpointer  user_data,
1283               GObject * details )
1284{
1285    struct cbdata * data = user_data;
1286    gpointer        hashString = g_hash_table_lookup( data->details2tor,
1287                                                      details );
1288
1289    g_hash_table_remove( data->details2tor, details );
1290    g_hash_table_remove( data->tor2details, hashString );
1291}
1292
1293static void
1294openFolderForeach( GtkTreeModel *           model,
1295                   GtkTreePath  * path      UNUSED,
1296                   GtkTreeIter *            iter,
1297                   gpointer       user_data UNUSED )
1298{
1299    TrTorrent * gtor = NULL;
1300
1301    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1302    tr_torrent_open_folder( gtor );
1303    g_object_unref( G_OBJECT( gtor ) );
1304}
1305
1306static void
1307showInfoForeach( GtkTreeModel *      model,
1308                 GtkTreePath  * path UNUSED,
1309                 GtkTreeIter *       iter,
1310                 gpointer            user_data )
1311{
1312    const char *    hashString;
1313    struct cbdata * data = user_data;
1314    TrTorrent *     tor = NULL;
1315    GtkWidget *     w;
1316
1317    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
1318    hashString = tr_torrent_info( tor )->hashString;
1319    w = g_hash_table_lookup( data->tor2details, hashString );
1320    if( w != NULL )
1321        gtk_window_present( GTK_WINDOW( w ) );
1322    else
1323    {
1324        w = torrent_inspector_new( GTK_WINDOW( data->wind ), tor );
1325        gtk_widget_show( w );
1326        g_hash_table_insert( data->tor2details, (gpointer)hashString, w );
1327        g_hash_table_insert( data->details2tor, w, (gpointer)hashString );
1328        g_object_weak_ref( G_OBJECT( w ), detailsClosed, data );
1329    }
1330
1331    g_object_unref( G_OBJECT( tor ) );
1332}
1333
1334static void
1335recheckTorrentForeach( GtkTreeModel *      model,
1336                       GtkTreePath  * path UNUSED,
1337                       GtkTreeIter *       iter,
1338                       gpointer       data UNUSED )
1339{
1340    TrTorrent * gtor = NULL;
1341
1342    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1343    tr_torrentVerify( tr_torrent_handle( gtor ) );
1344    g_object_unref( G_OBJECT( gtor ) );
1345}
1346
1347static gboolean
1348msgwinclosed( void )
1349{
1350    action_toggle( "toggle-message-log", FALSE );
1351    return FALSE;
1352}
1353
1354static void
1355accumulateSelectedTorrents( GtkTreeModel *      model,
1356                            GtkTreePath  * path UNUSED,
1357                            GtkTreeIter *       iter,
1358                            gpointer            gdata )
1359{
1360    GSList **   data = ( GSList** ) gdata;
1361    TrTorrent * tor = NULL;
1362
1363    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
1364    *data = g_slist_prepend( *data, tor );
1365}
1366
1367static void
1368removeSelected( struct cbdata * data,
1369                gboolean        delete_files )
1370{
1371    GSList *           l = NULL;
1372    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1373
1374    gtk_tree_selection_selected_foreach( s, accumulateSelectedTorrents, &l );
1375    gtk_tree_selection_unselect_all( s );
1376    if( l )
1377    {
1378        l = g_slist_reverse( l );
1379        confirmRemove( data->wind, data->core, l, delete_files );
1380    }
1381}
1382
1383static void
1384pauseAllTorrents( struct cbdata * data )
1385{
1386    tr_session * session = tr_core_session( data->core );
1387    const char * cmd = "{ \"method\": \"torrent-stop\" }";
1388    char * response = tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL );
1389    tr_free( response );
1390}
1391
1392void
1393doAction( const char * action_name, gpointer user_data )
1394{
1395    struct cbdata * data = user_data;
1396    gboolean        changed = FALSE;
1397
1398    if(  !strcmp( action_name, "add-torrent-menu" )
1399      || !strcmp( action_name, "add-torrent-toolbar" ) )
1400    {
1401        addDialog( data->wind, data->core );
1402    }
1403    else if( !strcmp( action_name, "show-stats" ) )
1404    {
1405        GtkWidget * dialog = stats_dialog_create( data->wind, data->core );
1406        gtk_widget_show( dialog );
1407    }
1408    else if( !strcmp( action_name, "start-torrent" ) )
1409    {
1410        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1411        gtk_tree_selection_selected_foreach( s, startTorrentForeach, NULL );
1412        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1413    }
1414    else if( !strcmp( action_name, "pause-all-torrents" ) )
1415    {
1416        pauseAllTorrents( data );
1417    }
1418    else if( !strcmp( action_name, "pause-torrent" ) )
1419    {
1420        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1421        gtk_tree_selection_selected_foreach( s, stopTorrentForeach, NULL );
1422        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1423    }
1424    else if( !strcmp( action_name, "verify-torrent" ) )
1425    {
1426        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1427        gtk_tree_selection_selected_foreach( s, recheckTorrentForeach, NULL );
1428        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1429    }
1430    else if( !strcmp( action_name, "open-torrent-folder" ) )
1431    {
1432        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1433        gtk_tree_selection_selected_foreach( s, openFolderForeach, data );
1434    }
1435    else if( !strcmp( action_name, "show-torrent-properties" ) )
1436    {
1437        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1438        gtk_tree_selection_selected_foreach( s, showInfoForeach, data );
1439    }
1440    else if( !strcmp( action_name, "update-tracker" ) )
1441    {
1442        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1443        gtk_tree_selection_selected_foreach( s, updateTrackerForeach,
1444                                             data->wind );
1445    }
1446    else if( !strcmp( action_name, "new-torrent" ) )
1447    {
1448        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ),
1449                                     tr_core_session( data->core ) );
1450        gtk_widget_show_all( w );
1451    }
1452    else if( !strcmp( action_name, "remove-torrent" ) )
1453    {
1454        removeSelected( data, FALSE );
1455    }
1456    else if( !strcmp( action_name, "delete-torrent" ) )
1457    {
1458        removeSelected( data, TRUE );
1459    }
1460    else if( !strcmp( action_name, "quit" ) )
1461    {
1462        askquit( data->core, data->wind, wannaquit, data );
1463    }
1464    else if( !strcmp( action_name, "select-all" ) )
1465    {
1466        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1467        gtk_tree_selection_select_all( s );
1468    }
1469    else if( !strcmp( action_name, "deselect-all" ) )
1470    {
1471        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1472        gtk_tree_selection_unselect_all( s );
1473    }
1474    else if( !strcmp( action_name, "edit-preferences" ) )
1475    {
1476        if( NULL == data->prefs )
1477        {
1478            data->prefs = tr_prefs_dialog_new( G_OBJECT( data->core ),
1479                                               data->wind );
1480            g_signal_connect( data->prefs, "destroy",
1481                              G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1482            gtk_widget_show( GTK_WIDGET( data->prefs ) );
1483        }
1484    }
1485    else if( !strcmp( action_name, "toggle-message-log" ) )
1486    {
1487        if( !data->msgwin )
1488        {
1489            GtkWidget * win = msgwin_new( data->core );
1490            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ),
1491                              NULL );
1492            data->msgwin = win;
1493        }
1494        else
1495        {
1496            action_toggle( "toggle-message-log", FALSE );
1497            gtk_widget_destroy( data->msgwin );
1498            data->msgwin = NULL;
1499        }
1500    }
1501    else if( !strcmp( action_name, "show-about-dialog" ) )
1502    {
1503        about( data->wind );
1504    }
1505    else if( !strcmp ( action_name, "help" ) )
1506    {
1507        char * url = gtr_get_help_url( );
1508        gtr_open_file( url );
1509        g_free( url );
1510    }
1511    else if( !strcmp( action_name, "toggle-main-window" ) )
1512    {
1513        toggleMainWindow( data, FALSE );
1514    }
1515    else if( !strcmp( action_name, "present-main-window" ) )
1516    {
1517        toggleMainWindow( data, TRUE );
1518    }
1519    else g_error ( "Unhandled action: %s", action_name );
1520
1521    if( changed )
1522        updatemodel( data );
1523}
1524
Note: See TracBrowser for help on using the repository browser.