source: trunk/gtk/main.c @ 7252

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

(gtk) #1300: "pause all" button in the right-click menu of the system tray icon

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