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

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

(1.4x gtk) fix some preferences bugs revealed by Rolcol's testing out changes in trunk

  • Property svn:keywords set to Date Rev Author Id
File size: 50.0 KB
Line 
1/******************************************************************************
2 * $Id: main.c 7395 2008-12-14 23:44:06Z 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    g_timeout_add( 60 * 1000, updateScheduledLimits,
656                  tr_core_session( cbdata->core ) );
657
658    /* either show the window or iconify it */
659    if( !minimized )
660        gtk_widget_show( GTK_WIDGET( wind ) );
661    else
662    {
663        gtk_window_iconify( wind );
664        gtk_window_set_skip_taskbar_hint( cbdata->wind,
665                                          cbdata->icon != NULL );
666    }
667}
668
669/**
670 * hideMainWindow, and the timeout hack in toggleMainWindow,
671 * are loosely cribbed from Colin Walters' rb-shell.c in Rhythmbox
672 */
673static gboolean
674idle_hide_mainwindow( gpointer window )
675{
676    gtk_widget_hide( window );
677    return FALSE;
678}
679
680static void
681hideMainWindow( struct cbdata * cbdata )
682{
683#if defined( STATUS_ICON_SUPPORTED ) && defined( GDK_WINDOWING_X11 )
684    GdkRectangle bounds;
685    gulong       data[4];
686    Display *    dpy;
687    GdkWindow *  gdk_window;
688
689    gtk_status_icon_get_geometry( GTK_STATUS_ICON(
690                                      cbdata->icon ), NULL, &bounds, NULL );
691    gdk_window = GTK_WIDGET ( cbdata->wind )->window;
692    dpy = gdk_x11_drawable_get_xdisplay ( gdk_window );
693
694    data[0] = bounds.x;
695    data[1] = bounds.y;
696    data[2] = bounds.width;
697    data[3] = bounds.height;
698
699    XChangeProperty ( dpy,
700                      GDK_WINDOW_XID ( gdk_window ),
701                      gdk_x11_get_xatom_by_name_for_display (
702                          gdk_drawable_get_display ( gdk_window ),
703                          "_NET_WM_ICON_GEOMETRY" ),
704                      XA_CARDINAL, 32, PropModeReplace,
705                      (guchar*)&data, 4 );
706
707    gtk_window_set_skip_taskbar_hint( cbdata->wind, TRUE );
708#endif
709    gtk_window_iconify( cbdata->wind );
710}
711
712static void
713clearTag( guint * tag )
714{
715    if( *tag )
716        g_source_remove( *tag );
717    *tag = 0;
718}
719
720static void
721toggleMainWindow( struct cbdata * cbdata,
722                  gboolean        present )
723{
724    GtkWindow * window = GTK_WINDOW( cbdata->wind );
725    const int   hide = !cbdata->minimized;
726    static int  x = 0, y = 0;
727
728    if( ( !present ) && hide )
729    {
730        gtk_window_get_position( window, &x, &y );
731        clearTag( &cbdata->idle_hide_mainwindow_tag );
732        hideMainWindow( cbdata );
733        cbdata->idle_hide_mainwindow_tag = g_timeout_add(
734            100, idle_hide_mainwindow, window );
735    }
736    else
737    {
738        gtk_window_set_skip_taskbar_hint( window, FALSE );
739        if( x != 0 && y != 0 )
740            gtk_window_move( window, x, y );
741        gtk_widget_show( GTK_WIDGET( window ) );
742        gtk_window_deiconify( window );
743#if GTK_CHECK_VERSION( 2, 8, 0 )
744        gtk_window_present_with_time( window, gtk_get_current_event_time( ) );
745#else
746        gtk_window_present( window );
747#endif
748    }
749}
750
751static gboolean
752winclose( GtkWidget * w    UNUSED,
753          GdkEvent * event UNUSED,
754          gpointer         gdata )
755{
756    struct cbdata * cbdata = gdata;
757
758    if( cbdata->icon != NULL )
759        action_activate ( "toggle-main-window" );
760    else
761        askquit( cbdata->core, cbdata->wind, wannaquit, cbdata );
762
763    return TRUE; /* don't propagate event further */
764}
765
766static void
767rowChangedCB( GtkTreeModel  * model UNUSED,
768              GtkTreePath *         path,
769              GtkTreeIter   * iter  UNUSED,
770              gpointer              sel )
771{
772    if( gtk_tree_selection_path_is_selected ( sel, path ) )
773        refreshTorrentActions( GTK_TREE_SELECTION( sel ) );
774}
775
776static void
777winsetup( struct cbdata * cbdata,
778          TrWindow *      wind )
779{
780    GtkTreeModel *     model;
781    GtkTreeSelection * sel;
782
783    g_assert( NULL == cbdata->wind );
784    cbdata->wind = GTK_WINDOW( wind );
785
786    sel = tr_window_get_selection( cbdata->wind );
787    g_signal_connect( sel, "changed", G_CALLBACK(
788                          selectionChangedCB ), NULL );
789    selectionChangedCB( sel, NULL );
790    model = tr_core_model( cbdata->core );
791    g_signal_connect( model, "row-changed", G_CALLBACK( rowChangedCB ), sel );
792    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
793    refreshTorrentActions( sel );
794
795    setupdrag( GTK_WIDGET( wind ), cbdata );
796}
797
798static gpointer
799quitThreadFunc( gpointer gdata )
800{
801    struct cbdata * cbdata = gdata;
802
803    tr_core_close( cbdata->core );
804
805    /* shutdown the gui */
806    if( cbdata->prefs )
807        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
808    if( cbdata->wind )
809        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
810    g_object_unref( cbdata->core );
811    if( cbdata->icon )
812        g_object_unref( cbdata->icon );
813    if( cbdata->errqueue )
814    {
815        g_slist_foreach( cbdata->errqueue, (GFunc)g_free, NULL );
816        g_slist_free( cbdata->errqueue );
817    }
818    if( cbdata->dupqueue )
819    {
820        g_slist_foreach( cbdata->dupqueue, (GFunc)g_free, NULL );
821        g_slist_free( cbdata->dupqueue );
822    }
823
824    g_hash_table_destroy( cbdata->details2tor );
825    g_hash_table_destroy( cbdata->tor2details );
826    g_free( cbdata );
827
828    /* exit the gtk main loop */
829    gtk_main_quit( );
830    return NULL;
831}
832
833static void
834do_exit_cb( GtkWidget *w  UNUSED,
835            gpointer data UNUSED )
836{
837    exit( 0 );
838}
839
840static void
841wannaquit( void * vdata )
842{
843    GtkWidget *     r, * p, * b, * w, *c;
844    struct cbdata * cbdata = vdata;
845
846    /* stop the update timer */
847    if( cbdata->timer )
848    {
849        g_source_remove( cbdata->timer );
850        cbdata->timer = 0;
851    }
852
853    c = GTK_WIDGET( cbdata->wind );
854    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
855
856    r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
857    gtk_container_add( GTK_CONTAINER( c ), r );
858
859    p = gtk_table_new( 3, 2, FALSE );
860    gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
861    gtk_container_add( GTK_CONTAINER( r ), p );
862
863    w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
864    gtk_table_attach_defaults( GTK_TABLE( p ), w, 0, 1, 0, 2 );
865
866    w = gtk_label_new( NULL );
867    gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
868    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
869    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
870
871    w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
872    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
873    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
874
875    b = gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
876    w = gtr_button_new_from_stock( GTK_STOCK_QUIT, _( "_Quit Now" ) );
877    g_signal_connect( w, "clicked", G_CALLBACK( do_exit_cb ), NULL );
878    gtk_container_add( GTK_CONTAINER( b ), w );
879    gtk_table_attach( GTK_TABLE(
880                          p ), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
881
882    gtk_widget_show_all( r );
883
884    /* clear the UI */
885    gtk_list_store_clear( GTK_LIST_STORE( tr_core_model( cbdata->core ) ) );
886
887    /* shut down libT */
888    g_thread_create( quitThreadFunc, vdata, TRUE, NULL );
889}
890
891static void
892gotdrag( GtkWidget         * widget UNUSED,
893         GdkDragContext *           dc,
894         gint                x      UNUSED,
895         gint                y      UNUSED,
896         GtkSelectionData *         sel,
897         guint               info   UNUSED,
898         guint                      time,
899         gpointer                   gdata )
900{
901    struct cbdata * data = gdata;
902    GSList *        paths = NULL;
903    GSList *        freeme = NULL;
904
905#if 0
906    int             i;
907    char *          sele = gdk_atom_name( sel->selection );
908    char *          targ = gdk_atom_name( sel->target );
909    char *          type = gdk_atom_name( sel->type );
910
911    g_message( "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i",
912               sele, targ, type, sel->format, sel->length );
913    g_free( sele );
914    g_free( targ );
915    g_free( type );
916    if( sel->format == 8 )
917    {
918        for( i = 0; i < sel->length; ++i )
919            fprintf( stderr, "%02X ", sel->data[i] );
920        fprintf( stderr, "\n" );
921    }
922#endif
923
924    if( ( sel->format == 8 )
925      && ( sel->selection == gdk_atom_intern( "XdndSelection", FALSE ) ) )
926    {
927        int      i;
928        char *   str = g_strndup( (char*)sel->data, sel->length );
929        gchar ** files = g_strsplit_set( str, "\r\n", -1 );
930        for( i = 0; files && files[i]; ++i )
931        {
932            char * filename;
933            if( !*files[i] ) /* empty filename... */
934                continue;
935
936            /* decode the filename */
937            filename = decode_uri( files[i] );
938            freeme = g_slist_prepend( freeme, filename );
939            if( !g_utf8_validate( filename, -1, NULL ) )
940                continue;
941
942            /* walk past "file://", if present */
943            if( g_str_has_prefix( filename, "file:" ) )
944            {
945                filename += 5;
946                while( g_str_has_prefix( filename, "//" ) )
947                    ++filename;
948            }
949
950            /* if the file doesn't exist, the first part
951               might be a hostname ... walk past it. */
952            if( !g_file_test( filename, G_FILE_TEST_EXISTS ) )
953            {
954                char * pch = strchr( filename + 1, '/' );
955                if( pch != NULL )
956                    filename = pch;
957            }
958
959            /* finally, add it to the list of torrents to try adding */
960            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
961                paths = g_slist_prepend( paths, g_strdup( filename ) );
962        }
963
964        /* try to add any torrents we found */
965        if( paths )
966        {
967            paths = g_slist_reverse( paths );
968            tr_core_add_list_defaults( data->core, paths );
969            tr_core_torrents_added( data->core );
970        }
971
972        freestrlist( freeme );
973        g_strfreev( files );
974        g_free( str );
975    }
976
977    gtk_drag_finish( dc, ( NULL != paths ), FALSE, time );
978}
979
980static void
981setupdrag( GtkWidget *    widget,
982           struct cbdata *data )
983{
984    GtkTargetEntry targets[] = {
985        { "STRING",          0, 0 },
986        { "text/plain",      0, 0 },
987        { "text/uri-list",   0, 0 },
988    };
989
990    g_signal_connect( widget, "drag_data_received", G_CALLBACK(
991                          gotdrag ), data );
992
993    gtk_drag_dest_set( widget, GTK_DEST_DEFAULT_ALL, targets,
994                       ALEN( targets ), GDK_ACTION_COPY | GDK_ACTION_MOVE );
995}
996
997static void
998flushAddTorrentErrors( GtkWindow *  window,
999                       const char * primary,
1000                       GSList **    files )
1001{
1002    GString *   s = g_string_new( NULL );
1003    GSList *    l;
1004    GtkWidget * w;
1005
1006    for( l = *files; l; l = l->next )
1007        g_string_append_printf( s, "%s\n", (const char*)l->data );
1008    w = gtk_message_dialog_new( window,
1009                                GTK_DIALOG_DESTROY_WITH_PARENT,
1010                                GTK_MESSAGE_ERROR,
1011                                GTK_BUTTONS_CLOSE,
1012                                "%s", primary );
1013    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
1014                                              "%s", s->str );
1015    g_signal_connect_swapped( w, "response",
1016                              G_CALLBACK( gtk_widget_destroy ), w );
1017    gtk_widget_show_all( w );
1018    g_string_free( s, TRUE );
1019
1020    g_slist_foreach( *files, (GFunc)g_free, NULL );
1021    g_slist_free( *files );
1022    *files = NULL;
1023}
1024
1025static void
1026showTorrentErrors( struct cbdata * cbdata )
1027{
1028    if( cbdata->errqueue )
1029        flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
1030                               ngettext( "Couldn't add corrupt torrent",
1031                                        "Couldn't add corrupt torrents",
1032                                        g_slist_length( cbdata->errqueue ) ),
1033                               &cbdata->errqueue );
1034
1035    if( cbdata->dupqueue )
1036        flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
1037                               ngettext( "Couldn't add duplicate torrent",
1038                                        "Couldn't add duplicate torrents",
1039                                        g_slist_length( cbdata->dupqueue ) ),
1040                               &cbdata->dupqueue );
1041}
1042
1043static void
1044coreerr( TrCore * core    UNUSED,
1045         enum tr_core_err code,
1046         const char *     msg,
1047         gpointer         gdata )
1048{
1049    struct cbdata * c = gdata;
1050
1051    switch( code )
1052    {
1053        case TR_EINVALID:
1054            c->errqueue =
1055                g_slist_append( c->errqueue, g_path_get_basename( msg ) );
1056            break;
1057
1058        case TR_EDUPLICATE:
1059            c->dupqueue =
1060                g_slist_append( c->dupqueue, g_path_get_basename( msg ) );
1061            break;
1062
1063        case TR_CORE_ERR_NO_MORE_TORRENTS:
1064            showTorrentErrors( c );
1065            break;
1066
1067        case TR_CORE_ERR_SAVE_STATE:
1068            errmsg( c->wind, "%s", msg );
1069            break;
1070
1071        default:
1072            g_assert_not_reached( );
1073            break;
1074    }
1075}
1076
1077#if GTK_CHECK_VERSION( 2, 8, 0 )
1078static void
1079on_main_window_focus_in( GtkWidget      * widget UNUSED,
1080                         GdkEventFocus  * event  UNUSED,
1081                         gpointer                gdata )
1082{
1083    struct cbdata * cbdata = gdata;
1084
1085    gtk_window_set_urgency_hint( GTK_WINDOW( cbdata->wind ), FALSE );
1086}
1087
1088#endif
1089
1090static void
1091onAddTorrent( TrCore *  core,
1092              tr_ctor * ctor,
1093              gpointer  gdata )
1094{
1095    struct cbdata * cbdata = gdata;
1096    GtkWidget *     w = addSingleTorrentDialog( cbdata->wind, core, ctor );
1097
1098#if GTK_CHECK_VERSION( 2, 8, 0 )
1099    g_signal_connect( w, "focus-in-event",
1100                      G_CALLBACK( on_main_window_focus_in ),  cbdata );
1101    gtk_window_set_urgency_hint( cbdata->wind, TRUE );
1102#endif
1103}
1104
1105static void
1106prefschanged( TrCore * core UNUSED,
1107              const char *  key,
1108              gpointer      data )
1109{
1110    struct cbdata  * cbdata = data;
1111    tr_session     * tr     = tr_core_session( cbdata->core );
1112
1113    if( !strcmp( key, PREF_KEY_ENCRYPTION ) )
1114    {
1115        const int encryption = pref_int_get( key );
1116        g_message( "setting encryption to %d", encryption );
1117        tr_sessionSetEncryption( tr, encryption );
1118    }
1119    else if( !strcmp( key, PREF_KEY_BLOCKLIST_ENABLED ) )
1120    {
1121        tr_blocklistSetEnabled( tr, pref_flag_get( key ) ); 
1122    }
1123    else if( !strcmp( key, PREF_KEY_DOWNLOAD_DIR ) )
1124    {
1125        tr_sessionSetDownloadDir( tr, pref_string_get( key ) );
1126    }
1127    else if( !strcmp( key, PREF_KEY_PORT ) )
1128    {
1129        const int port = pref_int_get( key );
1130        tr_sessionSetPeerPort( tr, port );
1131    }
1132    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
1133    {
1134        const int show = pref_flag_get( key );
1135        if( show && !cbdata->icon )
1136            cbdata->icon = tr_icon_new( cbdata->core );
1137        else if( !show && cbdata->icon )
1138        {
1139            g_object_unref( cbdata->icon );
1140            cbdata->icon = NULL;
1141        }
1142    }
1143    else if( !strcmp( key, PREF_KEY_DL_LIMIT_ENABLED ) )
1144    {
1145        const gboolean b = pref_flag_get( key );
1146        tr_sessionSetSpeedLimitEnabled( tr, TR_DOWN, b );
1147    }
1148    else if( !strcmp( key, PREF_KEY_DL_LIMIT ) )
1149    {
1150        const int limit = pref_int_get( key );
1151        tr_sessionSetSpeedLimit( tr, TR_DOWN, limit );
1152    }
1153    else if( !strcmp( key, PREF_KEY_UL_LIMIT_ENABLED ) )
1154    {
1155        const gboolean b = pref_flag_get( key );
1156        tr_sessionSetSpeedLimitEnabled( tr, TR_UP, b );
1157    }
1158    else if( !strcmp( key, PREF_KEY_UL_LIMIT ) )
1159    {
1160        const int limit = pref_int_get( key );
1161        tr_sessionSetSpeedLimit( tr, TR_UP, limit );
1162    }
1163    else if( !strncmp( key, "sched-", 6 ) )
1164    {
1165        updateScheduledLimits( tr );
1166    }
1167    else if( !strcmp( key, PREF_KEY_PORT_FORWARDING ) )
1168    {
1169        tr_sessionSetPortForwardingEnabled( tr, pref_flag_get( key ) );
1170    }
1171    else if( !strcmp( key, PREF_KEY_PEX ) )
1172    {
1173        tr_sessionSetPexEnabled( tr, pref_flag_get( key ) );
1174    }
1175    else if( !strcmp( key, PREF_KEY_RPC_ENABLED ) )
1176    {
1177        tr_sessionSetRPCEnabled( tr, pref_flag_get( key ) );
1178    }
1179    else if( !strcmp( key, PREF_KEY_RPC_PORT ) )
1180    {
1181        tr_sessionSetRPCPort( tr, pref_int_get( key ) );
1182    }
1183    else if( !strcmp( key, PREF_KEY_RPC_ENABLED ) )
1184    {
1185        tr_sessionSetRPCEnabled( tr, pref_flag_get( key ) );
1186    }
1187    else if( !strcmp( key, PREF_KEY_RPC_WHITELIST ) )
1188    {
1189        const char * s = pref_string_get( key );
1190        tr_sessionSetRPCWhitelist( tr, s );
1191    }
1192    else if( !strcmp( key, PREF_KEY_RPC_WHITELIST_ENABLED ) )
1193    {
1194        tr_sessionSetRPCWhitelistEnabled( tr, pref_flag_get( key ) );
1195    }
1196    else if( !strcmp( key, PREF_KEY_RPC_USERNAME ) )
1197    {
1198        const char * s = pref_string_get( key );
1199        tr_sessionSetRPCUsername( tr, s );
1200    }
1201    else if( !strcmp( key, PREF_KEY_RPC_PASSWORD ) )
1202    {
1203        const char * s = pref_string_get( key );
1204        tr_sessionSetRPCPassword( tr, s );
1205    }
1206    else if( !strcmp( key, PREF_KEY_RPC_AUTH_ENABLED ) )
1207    {
1208        const gboolean enabled = pref_flag_get( key );
1209        tr_sessionSetRPCPasswordEnabled( tr, enabled );
1210    }
1211    else if( !strcmp( key, PREF_KEY_PROXY_SERVER ) )
1212    {
1213        const char * s = pref_string_get( key );
1214        tr_sessionSetProxy( tr, s );
1215    }
1216    else if( !strcmp( key, PREF_KEY_PROXY_TYPE ) )
1217    {
1218        const int i = pref_int_get( key );
1219        tr_sessionSetProxyType( tr, i );
1220    }
1221    else if( !strcmp( key, PREF_KEY_PROXY_SERVER_ENABLED ) )
1222    {
1223        const gboolean enabled = pref_flag_get( key );
1224        tr_sessionSetProxyEnabled( tr, enabled );
1225    }
1226    else if( !strcmp( key, PREF_KEY_PROXY_AUTH_ENABLED ) )
1227    {
1228        const gboolean enabled = pref_flag_get( key );
1229        tr_sessionSetProxyAuthEnabled( tr, enabled );
1230    }
1231    else if( !strcmp( key, PREF_KEY_PROXY_USERNAME ) )
1232    {
1233        const char * s = pref_string_get( key );
1234        tr_sessionSetProxyUsername( tr, s );
1235    }
1236    else if( !strcmp( key, PREF_KEY_PROXY_PASSWORD ) )
1237    {
1238        const char * s = pref_string_get( key );
1239        tr_sessionSetProxyPassword( tr, s );
1240    }
1241    else if( !strcmp( key, PREF_KEY_PORT ) )
1242    {
1243        tr_sessionSetProxyPort( tr, pref_int_get( key ) );
1244    }
1245}
1246
1247static gboolean
1248updatemodel( gpointer gdata )
1249{
1250    struct cbdata *data = gdata;
1251    const gboolean done = data->closing || global_sigcount;
1252
1253    if( !done )
1254    {
1255        /* update the torrent data in the model */
1256        tr_core_update( data->core );
1257
1258        /* update the main window's statusbar and toolbar buttons */
1259        if( data->wind )
1260            tr_window_update( data->wind );
1261
1262        /* update the actions */
1263        refreshTorrentActions( tr_window_get_selection( data->wind ) );
1264    }
1265
1266    return !done;
1267}
1268
1269static void
1270aboutDialogActivateLink( GtkAboutDialog * dialog    UNUSED,
1271                         const gchar *              link_,
1272                         gpointer         user_data UNUSED )
1273{
1274    gtr_open_file( link_ );
1275}
1276
1277static void
1278about( GtkWindow * parent )
1279{
1280    const char *authors[] =
1281    {
1282        "Charles Kerr (Backend; GTK+)",
1283        "Mitchell Livingston (Backend; OS X)",
1284        "Eric Petit (Backend; OS X)",
1285        "Josh Elsasser (Daemon; Backend; GTK+)",
1286        "Bryan Varner (BeOS)",
1287        NULL
1288    };
1289
1290    const char *website_url = "http://www.transmissionbt.com/";
1291
1292    gtk_about_dialog_set_url_hook( aboutDialogActivateLink, NULL, NULL );
1293
1294    gtk_show_about_dialog( parent,
1295                           "name", g_get_application_name( ),
1296                           "comments",
1297                           _( "A fast and easy BitTorrent client" ),
1298                           "version", LONG_VERSION_STRING,
1299                           "website", website_url,
1300                           "website-label", website_url,
1301                           "copyright",
1302                           _(
1303                               "Copyright 2005-2008 The Transmission Project" ),
1304                           "logo-icon-name", "transmission",
1305#ifdef SHOW_LICENSE
1306                           "license", LICENSE,
1307                           "wrap-license", TRUE,
1308#endif
1309                           "authors", authors,
1310                           /* Translators: translate "translator-credits" as
1311                              your name
1312                              to have it appear in the credits in the "About"
1313                              dialog */
1314                           "translator-credits", _( "translator-credits" ),
1315                           NULL );
1316}
1317
1318static void
1319startTorrentForeach( GtkTreeModel *      model,
1320                     GtkTreePath  * path UNUSED,
1321                     GtkTreeIter *       iter,
1322                     gpointer       data UNUSED )
1323{
1324    tr_torrent * tor = NULL;
1325
1326    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1327    tr_torrentStart( tor );
1328}
1329
1330static void
1331stopTorrentForeach( GtkTreeModel *      model,
1332                    GtkTreePath  * path UNUSED,
1333                    GtkTreeIter *       iter,
1334                    gpointer       data UNUSED )
1335{
1336    tr_torrent * tor = NULL;
1337
1338    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1339    tr_torrentStop( tor );
1340}
1341
1342static void
1343updateTrackerForeach( GtkTreeModel *      model,
1344                      GtkTreePath  * path UNUSED,
1345                      GtkTreeIter *       iter,
1346                      gpointer       data UNUSED )
1347{
1348    tr_torrent * tor = NULL;
1349
1350    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1351    tr_torrentManualUpdate( tor );
1352}
1353
1354static void
1355detailsClosed( gpointer  user_data,
1356               GObject * details )
1357{
1358    struct cbdata * data = user_data;
1359    gpointer        hashString = g_hash_table_lookup( data->details2tor,
1360                                                      details );
1361
1362    g_hash_table_remove( data->details2tor, details );
1363    g_hash_table_remove( data->tor2details, hashString );
1364}
1365
1366static void
1367openFolderForeach( GtkTreeModel *           model,
1368                   GtkTreePath  * path      UNUSED,
1369                   GtkTreeIter *            iter,
1370                   gpointer       user_data UNUSED )
1371{
1372    TrTorrent * gtor = NULL;
1373
1374    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1375    tr_torrent_open_folder( gtor );
1376    g_object_unref( G_OBJECT( gtor ) );
1377}
1378
1379static void
1380showInfoForeach( GtkTreeModel *      model,
1381                 GtkTreePath  * path UNUSED,
1382                 GtkTreeIter *       iter,
1383                 gpointer            user_data )
1384{
1385    const char *    hashString;
1386    struct cbdata * data = user_data;
1387    TrTorrent *     tor = NULL;
1388    GtkWidget *     w;
1389
1390    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
1391    hashString = tr_torrent_info( tor )->hashString;
1392    w = g_hash_table_lookup( data->tor2details, hashString );
1393    if( w != NULL )
1394        gtk_window_present( GTK_WINDOW( w ) );
1395    else
1396    {
1397        w = torrent_inspector_new( GTK_WINDOW( data->wind ), tor );
1398        gtk_widget_show( w );
1399        g_hash_table_insert( data->tor2details, (gpointer)hashString, w );
1400        g_hash_table_insert( data->details2tor, w, (gpointer)hashString );
1401        g_object_weak_ref( G_OBJECT( w ), detailsClosed, data );
1402    }
1403
1404    g_object_unref( G_OBJECT( tor ) );
1405}
1406
1407static void
1408recheckTorrentForeach( GtkTreeModel *      model,
1409                       GtkTreePath  * path UNUSED,
1410                       GtkTreeIter *       iter,
1411                       gpointer       data UNUSED )
1412{
1413    TrTorrent * gtor = NULL;
1414
1415    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1416    tr_torrentVerify( tr_torrent_handle( gtor ) );
1417    g_object_unref( G_OBJECT( gtor ) );
1418}
1419
1420static gboolean
1421msgwinclosed( void )
1422{
1423    action_toggle( "toggle-message-log", FALSE );
1424    return FALSE;
1425}
1426
1427static void
1428accumulateSelectedTorrents( GtkTreeModel *      model,
1429                            GtkTreePath  * path UNUSED,
1430                            GtkTreeIter *       iter,
1431                            gpointer            gdata )
1432{
1433    GSList **   data = ( GSList** ) gdata;
1434    TrTorrent * tor = NULL;
1435
1436    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
1437    *data = g_slist_prepend( *data, tor );
1438}
1439
1440static void
1441removeSelected( struct cbdata * data,
1442                gboolean        delete_files )
1443{
1444    GSList *           l = NULL;
1445    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1446
1447    gtk_tree_selection_selected_foreach( s, accumulateSelectedTorrents, &l );
1448    gtk_tree_selection_unselect_all( s );
1449    if( l )
1450    {
1451        l = g_slist_reverse( l );
1452        confirmRemove( data->wind, data->core, l, delete_files );
1453    }
1454}
1455
1456void
1457doAction( const char * action_name,
1458          gpointer     user_data )
1459{
1460    struct cbdata * data = user_data;
1461    gboolean        changed = FALSE;
1462
1463    if( !strcmp ( action_name, "add-torrent-menu" )
1464      || !strcmp( action_name, "add-torrent-toolbar" ) )
1465    {
1466        addDialog( data->wind, data->core );
1467    }
1468    else if( !strcmp ( action_name, "show-stats" ) )
1469    {
1470        GtkWidget * dialog = stats_dialog_create( data->wind,
1471                                                  data->core );
1472        gtk_widget_show( dialog );
1473    }
1474    else if( !strcmp ( action_name, "start-torrent" ) )
1475    {
1476        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1477        gtk_tree_selection_selected_foreach( s, startTorrentForeach, NULL );
1478        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1479    }
1480    else if( !strcmp ( action_name, "pause-torrent" ) )
1481    {
1482        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1483        gtk_tree_selection_selected_foreach( s, stopTorrentForeach, NULL );
1484        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1485    }
1486    else if( !strcmp ( action_name, "verify-torrent" ) )
1487    {
1488        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1489        gtk_tree_selection_selected_foreach( s, recheckTorrentForeach, NULL );
1490        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1491    }
1492    else if( !strcmp ( action_name, "open-torrent-folder" ) )
1493    {
1494        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1495        gtk_tree_selection_selected_foreach( s, openFolderForeach, data );
1496    }
1497    else if( !strcmp ( action_name, "show-torrent-details" ) )
1498    {
1499        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1500        gtk_tree_selection_selected_foreach( s, showInfoForeach, data );
1501    }
1502    else if( !strcmp( action_name, "update-tracker" ) )
1503    {
1504        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1505        gtk_tree_selection_selected_foreach( s, updateTrackerForeach,
1506                                             data->wind );
1507    }
1508    else if( !strcmp ( action_name, "new-torrent" ) )
1509    {
1510        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ),
1511                                     tr_core_session( data->core ) );
1512        gtk_widget_show_all( w );
1513    }
1514    else if( !strcmp( action_name, "remove-torrent" ) )
1515    {
1516        removeSelected( data, FALSE );
1517    }
1518    else if( !strcmp( action_name, "delete-torrent" ) )
1519    {
1520        removeSelected( data, TRUE );
1521    }
1522    else if( !strcmp ( action_name, "quit" ) )
1523    {
1524        askquit( data->core, data->wind, wannaquit, data );
1525    }
1526    else if( !strcmp ( action_name, "select-all" ) )
1527    {
1528        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1529        gtk_tree_selection_select_all( s );
1530    }
1531    else if( !strcmp ( action_name, "deselect-all" ) )
1532    {
1533        GtkTreeSelection * s = tr_window_get_selection( data->wind );
1534        gtk_tree_selection_unselect_all( s );
1535    }
1536    else if( !strcmp ( action_name, "edit-preferences" ) )
1537    {
1538        if( NULL == data->prefs )
1539        {
1540            data->prefs = tr_prefs_dialog_new( G_OBJECT( data->core ),
1541                                               data->wind );
1542            g_signal_connect( data->prefs, "destroy",
1543                              G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1544            gtk_widget_show( GTK_WIDGET( data->prefs ) );
1545        }
1546    }
1547    else if( !strcmp ( action_name, "toggle-message-log" ) )
1548    {
1549        if( !data->msgwin )
1550        {
1551            GtkWidget * win = msgwin_new( data->core );
1552            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ),
1553                              NULL );
1554            data->msgwin = win;
1555        }
1556        else
1557        {
1558            action_toggle( "toggle-message-log", FALSE );
1559            gtk_widget_destroy( data->msgwin );
1560            data->msgwin = NULL;
1561        }
1562    }
1563    else if( !strcmp ( action_name, "show-about-dialog" ) )
1564    {
1565        about( data->wind );
1566    }
1567    else if( !strcmp ( action_name, "help" ) )
1568    {
1569        char * url = gtr_get_help_url( );
1570        gtr_open_file( url );
1571        g_free( url );
1572    }
1573    else if( !strcmp ( action_name, "toggle-main-window" ) )
1574    {
1575        toggleMainWindow( data, FALSE );
1576    }
1577    else if( !strcmp ( action_name, "present-main-window" ) )
1578    {
1579        toggleMainWindow( data, TRUE );
1580    }
1581    else g_error ( "Unhandled action: %s", action_name );
1582
1583    if( changed )
1584        updatemodel( data );
1585}
1586
Note: See TracBrowser for help on using the repository browser.