source: trunk/gtk/main.c @ 7112

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

(gtk) #1469: use "close" animation when sending to the notification area

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