source: branches/1.3x/gtk/main.c @ 6523

Last change on this file since 6523 was 6523, checked in by charles, 13 years ago

(1.3x) backport the memory leak fixes: r6519, r6516, and r6515

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