source: trunk/gtk/main.c @ 6400

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

(gtk) #1108: transmission includes several unlocalized strings

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