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

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

(1.5x gtk) minor backports:
(1) remove dead code
(2) minor improvements to the filterbar buttons
(3) various minor formatting changes to reduce the diffs between 1.52 and trunk

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