source: trunk/gtk/main.c @ 6678

Last change on this file since 6678 was 6678, checked in by muks, 14 years ago

Fix use of new Glib API which fails on older Glib build machines

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