source: branches/1.4x/gtk/main.c @ 7463

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

(1.4x gtk) #1585: use g_timeout_add_seconds() where appropriate to group timers together for fewer scheduled wakeups

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