source: branches/1.5x/gtk/main.c @ 8048

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

(1.5x gtk) launchpad bug #338046: XDG_DOWNLOAD_DIR isn't honored in gtk client

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