source: trunk/gtk/main.c @ 6556

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

#220: lazy bitfields

  • Property svn:keywords set to Date Rev Author Id
File size: 43.2 KB
Line 
1/******************************************************************************
2 * $Id: main.c 6556 2008-08-16 21:06:57Z 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 tr_rpc_callback_status
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    return TR_RPC_OK;
335}
336
337int
338main( int argc, char ** argv )
339{
340    char * err = NULL;
341    struct cbdata * cbdata;
342    GSList * argfiles;
343    GError * gerr;
344    gboolean didinit = FALSE;
345    gboolean didlock = FALSE;
346    gboolean startpaused = FALSE;
347    gboolean startminimized = FALSE;
348    char * domain = "transmission";
349    char * configDir = NULL;
350
351    GOptionEntry entries[] = {
352        { "paused", 'p', 0, G_OPTION_ARG_NONE, &startpaused,
353          _("Start with all torrents paused"), NULL },
354#ifdef STATUS_ICON_SUPPORTED
355        { "minimized", 'm', 0, G_OPTION_ARG_NONE, &startminimized,
356          _( "Start minimized in system tray"), NULL },
357#endif
358        { "config-dir", 'g', 0, G_OPTION_ARG_FILENAME, &configDir,
359          _( "Where to look for configuration files" ), NULL },
360        { NULL, 0, 0, 0, NULL, NULL, NULL }
361    };
362
363    cbdata = g_new0( struct cbdata, 1 );
364    cbdata->tor2details = g_hash_table_new( g_str_hash, g_str_equal );
365    cbdata->details2tor = g_hash_table_new( g_direct_hash, g_direct_equal );
366
367    /* bind the gettext domain */
368    setlocale( LC_ALL, "" );
369    bindtextdomain( domain, TRANSMISSIONLOCALEDIR );
370    bind_textdomain_codeset( domain, "UTF-8" );
371    textdomain( domain );
372    g_set_application_name( _( "Transmission" ) );
373
374    /* initialize gtk */
375    g_thread_init( NULL );
376    gerr = NULL;
377    if( !gtk_init_with_args( &argc, &argv, _("[torrent files]"), entries, domain, &gerr ) ) {
378        g_message( "%s", gerr->message );
379        g_clear_error( &gerr );
380        return 0;
381    }
382    if( configDir == NULL )
383        configDir = (char*) tr_getDefaultConfigDir( );
384
385    tr_notify_init( );
386
387    didinit = cf_init( configDir, NULL ); /* must come before actions_init */
388    tr_prefs_init_global( );
389    myUIManager = gtk_ui_manager_new ();
390    actions_init ( myUIManager, cbdata );
391    gtk_ui_manager_add_ui_from_string (myUIManager, fallback_ui_file, -1, NULL);
392    gtk_ui_manager_ensure_update (myUIManager);
393    gtk_window_set_default_icon_name ( "transmission" );
394
395    setupsighandlers( ); /* set up handlers for fatal signals */
396
397    /* either get a lockfile s.t. this is the one instance of
398     * transmission that's running, OR if there are files to
399     * be added, delegate that to the running instance via dbus */
400    didlock = cf_lock( &err );
401    argfiles = checkfilenames( argc-1, argv+1 );
402    if( !didlock && argfiles )
403    {
404        GSList * l;
405        gboolean delegated = FALSE;
406        for( l=argfiles; l; l=l->next )
407            delegated |= gtr_dbus_add_torrent( l->data );
408        if( delegated )
409            err = NULL;
410    }
411
412    if( didlock && ( didinit || cf_init( configDir, &err ) ) )
413    {
414        GtkWindow * win;
415
416        tr_handle * h = tr_sessionInitFull(
417                            configDir,
418                            "gtk",
419                            pref_string_get( PREF_KEY_DOWNLOAD_DIR ),
420                            pref_flag_get( PREF_KEY_PEX ),
421                            pref_flag_get( PREF_KEY_PORT_FORWARDING ),
422                            pref_int_get( PREF_KEY_PORT ),
423                            pref_int_get( PREF_KEY_ENCRYPTION ),
424                            pref_flag_get( PREF_KEY_LAZY_BITFIELD ),
425                            pref_flag_get( PREF_KEY_UL_LIMIT_ENABLED ),
426                            pref_int_get( PREF_KEY_UL_LIMIT ),
427                            pref_flag_get( PREF_KEY_DL_LIMIT_ENABLED ),
428                            pref_int_get( PREF_KEY_DL_LIMIT ),
429                            pref_int_get( PREF_KEY_MAX_PEERS_GLOBAL ),
430                            pref_int_get( PREF_KEY_MSGLEVEL ),
431                            TRUE, /* message queueing */
432                            pref_flag_get( PREF_KEY_BLOCKLIST_ENABLED ),
433                            pref_int_get( PREF_KEY_PEER_SOCKET_TOS ),
434                            pref_flag_get( PREF_KEY_RPC_ENABLED ),
435                            pref_int_get( PREF_KEY_RPC_PORT ),
436                            pref_string_get( PREF_KEY_RPC_ACL ),
437                            pref_flag_get( PREF_KEY_RPC_AUTH_ENABLED ),
438                            pref_string_get( PREF_KEY_RPC_USERNAME ),
439                            pref_string_get( PREF_KEY_RPC_PASSWORD ),
440                            pref_flag_get( PREF_KEY_PROXY_SERVER_ENABLED ),
441                            pref_string_get( PREF_KEY_PROXY_SERVER ),
442                            pref_int_get( PREF_KEY_PROXY_PORT ),
443                            pref_int_get( PREF_KEY_PROXY_TYPE ),
444                            pref_flag_get( PREF_KEY_PROXY_AUTH_ENABLED ),
445                            pref_string_get( PREF_KEY_PROXY_USERNAME ),
446                            pref_string_get( PREF_KEY_PROXY_PASSWORD ) );
447
448        cbdata->core = tr_core_new( h );
449
450        /* create main window now to be a parent to any error dialogs */
451        win = GTK_WINDOW( tr_window_new( myUIManager, cbdata->core ) );
452        g_signal_connect( win, "window-state-event", G_CALLBACK(windowStateChanged), cbdata );
453        g_signal_connect( win, "size-allocate", G_CALLBACK(onMainWindowSizeAllocated), cbdata );
454
455        appsetup( win, argfiles, cbdata, startpaused, startminimized );
456        tr_sessionSetRPCCallback( h, onRPCChanged, cbdata );
457
458        gtk_main();
459    }
460    else if( err )
461    {
462        gtk_widget_show( errmsg_full( NULL, (callbackfunc_t)gtk_main_quit,
463                                      NULL, "%s", err ) );
464        g_free( err );
465        gtk_main();
466    }
467
468    return 0;
469}
470
471static void
472appsetup( TrWindow * wind, GSList * torrentFiles,
473          struct cbdata * cbdata,
474          gboolean forcepause, gboolean minimized )
475{
476    const pref_flag_t start = forcepause ? PREF_FLAG_FALSE : PREF_FLAG_DEFAULT;
477    const pref_flag_t prompt = PREF_FLAG_DEFAULT;
478
479    /* fill out cbdata */
480    cbdata->wind       = NULL;
481    cbdata->icon       = NULL;
482    cbdata->msgwin     = NULL;
483    cbdata->prefs      = NULL;
484    cbdata->timer      = 0;
485    cbdata->closing    = FALSE;
486    cbdata->errqueue   = NULL;
487    cbdata->dupqueue   = NULL;
488    cbdata->minimized  = minimized;
489
490    if( minimized )
491        pref_flag_set( PREF_KEY_SHOW_TRAY_ICON, TRUE );
492
493    actions_set_core( cbdata->core );
494
495    /* set up core handlers */
496    g_signal_connect( cbdata->core, "error", G_CALLBACK( coreerr ), cbdata );
497    g_signal_connect( cbdata->core, "add-torrent-prompt",
498                      G_CALLBACK( onAddTorrent ), cbdata );
499    g_signal_connect_swapped( cbdata->core, "quit",
500                              G_CALLBACK( wannaquit ), cbdata );
501    g_signal_connect( cbdata->core, "prefs-changed",
502                      G_CALLBACK( prefschanged ), cbdata );
503
504    /* add torrents from command-line and saved state */
505    tr_core_load( cbdata->core, forcepause );
506    tr_core_add_list( cbdata->core, torrentFiles, start, prompt );
507    torrentFiles = NULL;
508    tr_core_torrents_added( cbdata->core );
509
510    /* set up main window */
511    winsetup( cbdata, wind );
512
513    /* set up the icon */
514    prefschanged( cbdata->core, PREF_KEY_SHOW_TRAY_ICON, cbdata );
515
516    /* start model update timer */
517    cbdata->timer = g_timeout_add( UPDATE_INTERVAL, updatemodel, cbdata );
518    updatemodel( cbdata );
519
520    /* either show the window or iconify it */
521    if( !minimized )
522        gtk_widget_show( GTK_WIDGET( wind ) );
523    else {
524        gtk_window_iconify( wind );
525        gtk_window_set_skip_taskbar_hint( cbdata->wind, cbdata->icon != NULL );
526    }
527}
528
529
530/**
531 * hideMainWindow, and the timeout hack in toggleMainWindow,
532 * are loosely cribbed from Colin Walters' tr-shell.c in Rhythmbox
533 */
534static gboolean
535idle_hide_mainwindow( gpointer window )
536{
537    gtk_widget_hide( window );
538    return FALSE;
539}
540static void
541hideMainWindow( struct cbdata * cbdata )
542{
543#if defined(STATUS_ICON_SUPPORTED) && defined(GDK_WINDOWING_X11)
544    GdkRectangle  bounds;
545    gulong        data[4];
546    Display      *dpy;
547    GdkWindow    *gdk_window;
548
549    gtk_status_icon_get_geometry( GTK_STATUS_ICON( cbdata->icon ), NULL, &bounds, NULL );
550    gdk_window = GTK_WIDGET (cbdata->wind)->window;
551    dpy = gdk_x11_drawable_get_xdisplay (gdk_window);
552
553    data[0] = bounds.x;
554    data[1] = bounds.y;
555    data[2] = bounds.width;
556    data[3] = bounds.height;
557
558    XChangeProperty (dpy,
559                     GDK_WINDOW_XID (gdk_window),
560                     gdk_x11_get_xatom_by_name_for_display (gdk_drawable_get_display (gdk_window),
561                     "_NET_WM_ICON_GEOMETRY"),
562                     XA_CARDINAL, 32, PropModeReplace,
563                     (guchar*)&data, 4);
564
565    gtk_window_set_skip_taskbar_hint( cbdata->wind, TRUE );
566#endif
567    gtk_window_iconify( cbdata->wind );
568}
569
570static void
571clearTag( guint * tag )
572{
573    if( *tag )
574        g_source_remove( *tag );
575    *tag = 0;
576}
577
578static void
579toggleMainWindow( struct cbdata * cbdata )
580{
581    GtkWindow * window = GTK_WINDOW( cbdata->wind );
582    const int hide = !cbdata->minimized;
583    static int x=0, y=0;
584
585    if( hide )
586    {
587        gtk_window_get_position( window, &x, &y );
588        clearTag( &cbdata->idle_hide_mainwindow_tag );
589        hideMainWindow( cbdata );
590        cbdata->idle_hide_mainwindow_tag = g_timeout_add( 250, idle_hide_mainwindow, window );
591    }
592    else
593    {
594        gtk_window_set_skip_taskbar_hint( window, FALSE );
595        gtk_window_move( window, x, y );
596        gtk_widget_show( GTK_WIDGET( window ) );
597        gtk_window_deiconify( window );
598#if GTK_CHECK_VERSION(2,8,0)
599        gtk_window_present_with_time( window, gtk_get_current_event_time( ) );
600#else
601        gtk_window_present( window );
602#endif
603    }
604}
605
606static gboolean
607winclose( GtkWidget * w UNUSED, GdkEvent * event UNUSED, gpointer gdata )
608{
609    struct cbdata * cbdata = gdata;
610
611    if( cbdata->icon != NULL )
612        action_activate ("toggle-main-window");
613    else
614        askquit( cbdata->core, cbdata->wind, wannaquit, cbdata );
615
616    return TRUE; /* don't propagate event further */
617}
618
619static void
620rowChangedCB( GtkTreeModel  * model UNUSED,
621              GtkTreePath   * path,
622              GtkTreeIter   * iter UNUSED,
623              gpointer        sel)
624{
625    if( gtk_tree_selection_path_is_selected ( sel, path ) )
626        refreshTorrentActions( GTK_TREE_SELECTION(sel) );
627}
628
629static void
630winsetup( struct cbdata * cbdata, TrWindow * wind )
631{
632    GtkTreeModel * model;
633    GtkTreeSelection * sel;
634
635    g_assert( NULL == cbdata->wind );
636    cbdata->wind = GTK_WINDOW( wind );
637
638    sel = tr_window_get_selection( cbdata->wind );
639    g_signal_connect( sel, "changed", G_CALLBACK(selectionChangedCB), NULL );
640    selectionChangedCB( sel, NULL );
641    model = tr_core_model( cbdata->core );
642    g_signal_connect( model, "row-changed", G_CALLBACK(rowChangedCB), sel );
643    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
644    refreshTorrentActions( sel );
645   
646    setupdrag( GTK_WIDGET(wind), cbdata );
647}
648
649static gpointer
650quitThreadFunc( gpointer gdata )
651{
652    struct cbdata * cbdata = gdata;
653
654    tr_core_close( cbdata->core );
655
656    /* shutdown the gui */
657    if( cbdata->prefs )
658        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
659    if( cbdata->wind )
660        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
661    g_object_unref( cbdata->core );
662    if( cbdata->icon )
663        g_object_unref( cbdata->icon );
664    if( cbdata->errqueue ) {
665        g_slist_foreach( cbdata->errqueue, (GFunc)g_free, NULL );
666        g_slist_free( cbdata->errqueue );
667    }
668    if( cbdata->dupqueue ) {
669        g_slist_foreach( cbdata->dupqueue, (GFunc)g_free, NULL );
670        g_slist_free( cbdata->dupqueue );
671    }
672
673    g_hash_table_destroy( cbdata->details2tor );
674    g_hash_table_destroy( cbdata->tor2details );
675    g_free( cbdata );
676
677    /* exit the gtk main loop */
678    gtk_main_quit( );
679    return NULL;
680}
681
682static void
683do_exit_cb( GtkWidget *w UNUSED, gpointer data UNUSED )
684{
685    exit( 0 );
686}
687
688static void
689wannaquit( void * vdata )
690{
691    GtkWidget * r, * p, * b, * w, *c;
692    struct cbdata * cbdata = vdata;
693
694    /* stop the update timer */
695    if( cbdata->timer ) {
696        g_source_remove( cbdata->timer );
697        cbdata->timer = 0;
698    }
699
700    c = GTK_WIDGET( cbdata->wind );
701    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
702
703    r = gtk_alignment_new(0.5, 0.5, 0.01, 0.01);
704    gtk_container_add(GTK_CONTAINER(c), r);
705
706    p = gtk_table_new(3, 2, FALSE);
707    gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
708    gtk_container_add( GTK_CONTAINER( r ), p );
709
710    w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
711    gtk_table_attach_defaults(GTK_TABLE(p), w, 0, 1, 0, 2 );
712
713    w = gtk_label_new( NULL );
714    gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
715    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
716    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
717
718    w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
719    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
720    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
721
722    b = gtk_alignment_new(0.0, 1.0, 0.01, 0.01);
723    w = tr_button_new_from_stock( GTK_STOCK_QUIT, _( "_Quit Now" ) );
724    g_signal_connect(w, "clicked", G_CALLBACK(do_exit_cb), NULL);
725    gtk_container_add(GTK_CONTAINER(b), w);
726    gtk_table_attach(GTK_TABLE(p), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
727
728    gtk_widget_show_all(r);
729
730    /* clear the UI */
731    gtk_list_store_clear( GTK_LIST_STORE( tr_core_model( cbdata->core ) ) );
732
733    /* shut down libT */
734    g_thread_create( quitThreadFunc, vdata, TRUE, NULL );
735}
736
737static void
738gotdrag( GtkWidget         * widget UNUSED,
739         GdkDragContext    * dc,
740         gint                x UNUSED,
741         gint                y UNUSED,
742         GtkSelectionData  * sel,
743         guint               info UNUSED,
744         guint               time,
745         gpointer            gdata )
746{
747    struct cbdata * data = gdata;
748    GSList * paths = NULL;
749    GSList * freeme = NULL;
750
751#if 0
752    int i;
753    char *sele = gdk_atom_name(sel->selection);
754    char *targ = gdk_atom_name(sel->target);
755    char *type = gdk_atom_name(sel->type);
756
757    g_message( "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i",
758               sele, targ, type, sel->format, sel->length );
759    g_free(sele);
760    g_free(targ);
761    g_free(type);
762    if( sel->format == 8 ) {
763        for( i=0; i<sel->length; ++i )
764            fprintf(stderr, "%02X ", sel->data[i]);
765        fprintf(stderr, "\n");
766    }
767#endif
768
769    if( ( sel->format == 8 ) &&
770        ( sel->selection == gdk_atom_intern( "XdndSelection", FALSE ) ) )
771    {
772        int i;
773        char * str = g_strndup( (char*)sel->data, sel->length );
774        gchar ** files = g_strsplit_set( str, "\r\n", -1 );
775        for( i=0; files && files[i]; ++i )
776        {
777            char * filename;
778            if( !*files[i] ) /* empty filename... */
779                continue;
780
781            /* decode the filename */
782            filename = decode_uri( files[i] );
783            freeme = g_slist_prepend( freeme, filename );
784            if( !g_utf8_validate( filename, -1, NULL ) )
785                continue;
786
787            /* walk past "file://", if present */
788            if( g_str_has_prefix( filename, "file:" ) ) {
789                filename += 5;
790                while( g_str_has_prefix( filename, "//" ) )
791                    ++filename;
792            }
793
794            /* if the file doesn't exist, the first part
795               might be a hostname ... walk past it. */
796            if( !g_file_test( filename, G_FILE_TEST_EXISTS ) ) {
797                char * pch = strchr( filename + 1, '/' );
798                if( pch != NULL )
799                    filename = pch;
800            }
801
802            /* finally, add it to the list of torrents to try adding */
803            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
804                paths = g_slist_prepend( paths, g_strdup( filename ) );
805        }
806
807        /* try to add any torrents we found */
808        if( paths )
809        {
810            paths = g_slist_reverse( paths );
811            tr_core_add_list_defaults( data->core, paths );
812            tr_core_torrents_added( data->core );
813        }
814
815        freestrlist( freeme );
816        g_strfreev( files );
817        g_free( str );
818    }
819
820    gtk_drag_finish(dc, (NULL != paths), FALSE, time);
821}
822
823static void
824setupdrag(GtkWidget *widget, struct cbdata *data) {
825  GtkTargetEntry targets[] = {
826    { "STRING",     0, 0 },
827    { "text/plain", 0, 0 },
828    { "text/uri-list", 0, 0 },
829  };
830
831  g_signal_connect(widget, "drag_data_received", G_CALLBACK(gotdrag), data);
832
833  gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, targets,
834                    ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE);
835}
836
837static void
838flushAddTorrentErrors( GtkWindow * window, const char * primary, GSList ** files )
839{
840    GString * s = g_string_new( NULL );
841    GSList * l;
842    GtkWidget * w;
843    for( l=*files; l; l=l->next )
844        g_string_append_printf( s, "%s\n", (const char*)l->data );
845    w = gtk_message_dialog_new( window,
846                                GTK_DIALOG_DESTROY_WITH_PARENT,
847                                GTK_MESSAGE_ERROR,
848                                GTK_BUTTONS_CLOSE,
849                                primary );
850    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), s->str );
851    g_signal_connect_swapped( w, "response",
852                              G_CALLBACK( gtk_widget_destroy ), w );
853    gtk_widget_show_all( w );
854    g_string_free( s, TRUE );
855
856    g_slist_foreach( *files, (GFunc)g_free, NULL );
857    g_slist_free( *files );
858    *files = NULL;
859}
860
861static void
862showTorrentErrors( struct cbdata * cbdata )
863{
864    if( cbdata->errqueue )
865        flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
866                               ngettext( "Couldn't add corrupt torrent",
867                                         "Couldn't add corrupt torrents",
868                                         g_slist_length( cbdata->errqueue ) ),
869                               &cbdata->errqueue );
870
871    if( cbdata->dupqueue )
872        flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
873                               ngettext( "Couldn't add duplicate torrent",
874                                         "Couldn't add duplicate torrents",
875                                         g_slist_length( cbdata->dupqueue ) ),
876                               &cbdata->dupqueue );
877}
878
879static void
880coreerr( TrCore * core UNUSED, enum tr_core_err code, const char * msg,
881         gpointer gdata )
882{
883    struct cbdata * c = gdata;
884
885    switch( code )
886    {
887        case TR_EINVALID:
888            c->errqueue = g_slist_append( c->errqueue, g_path_get_basename( msg ) );
889            break;
890
891        case TR_EDUPLICATE:
892            c->dupqueue = g_slist_append( c->dupqueue, g_path_get_basename( msg ) );
893            break;
894
895        case TR_CORE_ERR_NO_MORE_TORRENTS:
896            showTorrentErrors( c );
897            break;
898
899        case TR_CORE_ERR_SAVE_STATE:
900            errmsg( c->wind, "%s", msg );
901            break;
902
903        default:
904            g_assert_not_reached();
905            break;
906    }
907
908}
909
910#if GTK_CHECK_VERSION(2,8,0)
911static void
912on_main_window_focus_in( GtkWidget      * widget UNUSED,
913                         GdkEventFocus  * event UNUSED,
914                         gpointer         gdata )
915{
916    struct cbdata * cbdata = gdata;
917    gtk_window_set_urgency_hint( GTK_WINDOW( cbdata->wind ), FALSE );
918}
919#endif
920
921static void
922onAddTorrent( TrCore * core, tr_ctor * ctor, gpointer gdata )
923{
924    struct cbdata * cbdata = gdata;
925    GtkWidget * w = addSingleTorrentDialog( cbdata->wind, core, ctor );
926#if GTK_CHECK_VERSION(2,8,0)
927    g_signal_connect( w, "focus-in-event", G_CALLBACK(on_main_window_focus_in),  cbdata );
928    gtk_window_set_urgency_hint( cbdata->wind, TRUE );
929#endif
930}
931
932static void
933prefschanged( TrCore * core UNUSED, const char * key, gpointer data )
934{
935    struct cbdata * cbdata = data;
936    tr_handle     * tr     = tr_core_handle( cbdata->core );
937
938    if( !strcmp( key, PREF_KEY_ENCRYPTION ) )
939    {
940        const int encryption = pref_int_get( key );
941g_message( "setting encryption to %d", encryption );
942        tr_sessionSetEncryption( tr, encryption );
943    }
944    else if( !strcmp( key, PREF_KEY_PORT ) )
945    {
946        const int port = pref_int_get( key );
947        tr_sessionSetPeerPort( tr, port );
948    }
949    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
950    {
951        const int show = pref_flag_get( key );
952        if( show && !cbdata->icon )
953            cbdata->icon = tr_icon_new( cbdata->core );
954        else if( !show && cbdata->icon ) {
955            g_object_unref( cbdata->icon );
956            cbdata->icon = NULL;
957        }
958    }
959    else if( !strcmp( key, PREF_KEY_DL_LIMIT_ENABLED ) )
960    {
961        const gboolean b = pref_flag_get( key );
962        tr_sessionSetSpeedLimitEnabled( tr, TR_DOWN, b );
963    }
964    else if( !strcmp( key, PREF_KEY_DL_LIMIT ) )
965    {
966        const int limit = pref_int_get( key );
967        tr_sessionSetSpeedLimit( tr, TR_DOWN, limit );
968    }
969    else if( !strcmp( key, PREF_KEY_UL_LIMIT_ENABLED ) )
970    {
971        const gboolean b = pref_flag_get( key );
972        tr_sessionSetSpeedLimitEnabled( tr, TR_UP, b );
973    }
974    else if( !strcmp( key, PREF_KEY_UL_LIMIT ) )
975    {
976        const int limit = pref_int_get( key );
977        tr_sessionSetSpeedLimit( tr, TR_UP, limit );
978    }
979    else if( !strcmp( key, PREF_KEY_PORT_FORWARDING ) )
980    {
981        const gboolean enabled = pref_flag_get( key );
982        tr_sessionSetPortForwardingEnabled( tr, enabled );
983    }
984    else if( !strcmp( key, PREF_KEY_PEX ) )
985    {
986        const gboolean b = pref_flag_get( key );
987        tr_sessionSetPortForwardingEnabled( tr, b );
988    }
989    else if( !strcmp( key, PREF_KEY_RPC_ENABLED ) )
990    {
991        tr_sessionSetRPCEnabled( tr, pref_flag_get( key ) );
992    }
993    else if( !strcmp( key, PREF_KEY_RPC_PORT ) )
994    {
995        tr_sessionSetRPCPort( tr, pref_int_get( key ) );
996    }
997    else if( !strcmp( key, PREF_KEY_RPC_ENABLED ) )
998    {
999        tr_sessionSetRPCEnabled( tr, pref_flag_get( key ) );
1000    }
1001    else if( !strcmp( key, PREF_KEY_RPC_ACL ) )
1002    {
1003        const char * s = pref_string_get( key );
1004        tr_sessionSetRPCACL( tr, s, NULL );
1005    }
1006    else if( !strcmp( key, PREF_KEY_RPC_USERNAME ) )
1007    {
1008        const char * s = pref_string_get( key );
1009        tr_sessionSetRPCUsername( tr, s );
1010    }
1011    else if( !strcmp( key, PREF_KEY_RPC_PASSWORD ) )
1012    {
1013        const char * s = pref_string_get( key );
1014        tr_sessionSetRPCPassword( tr, s );
1015    }
1016    else if( !strcmp( key, PREF_KEY_RPC_AUTH_ENABLED ) )
1017    {
1018        const gboolean enabled = pref_flag_get( key );
1019        tr_sessionSetRPCPasswordEnabled( tr, enabled );
1020    }
1021    else if( !strcmp( key, PREF_KEY_PROXY_SERVER ) )
1022    {
1023        const char * s = pref_string_get( key );
1024        tr_sessionSetProxy( tr, s );
1025    }
1026    else if( !strcmp( key, PREF_KEY_PROXY_TYPE ) )
1027    {
1028        const int i = pref_int_get( key );
1029        tr_sessionSetProxyType( tr, i );
1030    }
1031    else if( !strcmp( key, PREF_KEY_PROXY_SERVER_ENABLED ) )
1032    {
1033        const gboolean enabled = pref_flag_get( key );
1034        tr_sessionSetProxyEnabled( tr, enabled );
1035    }
1036    else if( !strcmp( key, PREF_KEY_PROXY_AUTH_ENABLED ) )
1037    {
1038        const gboolean enabled = pref_flag_get( key );
1039        tr_sessionSetProxyAuthEnabled( tr, enabled );
1040    }
1041    else if( !strcmp( key, PREF_KEY_PROXY_USERNAME ) )
1042    {
1043        const char * s = pref_string_get( key );
1044        tr_sessionSetProxyUsername( tr, s );
1045    }
1046    else if( !strcmp( key, PREF_KEY_PROXY_PASSWORD ) )
1047    {
1048        const char * s = pref_string_get( key );
1049        tr_sessionSetProxyPassword( tr, s );
1050    }
1051}
1052
1053static gboolean
1054updatemodel( gpointer gdata )
1055{
1056    struct cbdata *data = gdata;
1057    const gboolean done = data->closing || global_sigcount;
1058
1059    if( !done )
1060    {
1061        /* update the torrent data in the model */
1062        tr_core_update( data->core );
1063
1064        /* update the main window's statusbar and toolbar buttons */
1065        if( data->wind )
1066            tr_window_update( data->wind );
1067
1068        /* update the actions */
1069        refreshTorrentActions( tr_window_get_selection( data->wind ) );
1070    }
1071
1072    return !done;
1073}
1074
1075static void
1076aboutDialogActivateLink( GtkAboutDialog * dialog UNUSED,
1077                         const gchar    * link_,
1078                         gpointer         user_data UNUSED )
1079{
1080    gtr_open_file( link_ );
1081}
1082
1083static void
1084about ( GtkWindow * parent )
1085{
1086    const char *authors[] =
1087    {
1088        "Charles Kerr (Backend; GTK+)",
1089        "Mitchell Livingston (Backend; OS X)",
1090        "Eric Petit (Backend; OS X)",
1091        "Josh Elsasser (Daemon; Backend; GTK+)",
1092        "Bryan Varner (BeOS)", 
1093        NULL
1094    };
1095
1096    const char *website_url = "http://www.transmissionbt.com/";
1097
1098    gtk_about_dialog_set_url_hook( aboutDialogActivateLink, NULL, NULL );
1099
1100    gtk_show_about_dialog( parent,
1101        "name", g_get_application_name(),
1102        "comments", _("A fast and easy BitTorrent client"),
1103        "version", LONG_VERSION_STRING,
1104        "website", website_url,
1105        "website-label", website_url,
1106        "copyright",_("Copyright 2005-2008 The Transmission Project"),
1107        "logo-icon-name", "transmission",
1108#ifdef SHOW_LICENSE
1109        "license", LICENSE,
1110        "wrap-license", TRUE,
1111#endif
1112        "authors", authors,
1113        /* Translators: translate "translator-credits" as your name
1114           to have it appear in the credits in the "About" dialog */
1115        "translator-credits", _("translator-credits"),
1116        NULL );
1117}
1118
1119static void
1120startTorrentForeach (GtkTreeModel * model,
1121                     GtkTreePath  * path UNUSED,
1122                     GtkTreeIter  * iter,
1123                     gpointer       data UNUSED)
1124{
1125    tr_torrent * tor = NULL;
1126    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1127    tr_torrentStart( tor );
1128}
1129
1130static void
1131stopTorrentForeach (GtkTreeModel * model,
1132                    GtkTreePath  * path UNUSED,
1133                    GtkTreeIter  * iter,
1134                    gpointer       data UNUSED)
1135{
1136    tr_torrent * tor = NULL;
1137    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1138    tr_torrentStop( tor );
1139}
1140
1141static void
1142updateTrackerForeach (GtkTreeModel * model,
1143                      GtkTreePath  * path UNUSED,
1144                      GtkTreeIter  * iter,
1145                      gpointer       data UNUSED)
1146{
1147    tr_torrent * tor = NULL;
1148    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
1149    tr_torrentManualUpdate( tor );
1150}
1151
1152static void
1153detailsClosed( gpointer user_data, GObject * details )
1154{
1155    struct cbdata * data = user_data;
1156    gpointer hashString = g_hash_table_lookup( data->details2tor, details );
1157    g_hash_table_remove( data->details2tor, details );
1158    g_hash_table_remove( data->tor2details, hashString );
1159}
1160
1161static void
1162openFolderForeach( GtkTreeModel * model,
1163                   GtkTreePath  * path UNUSED,
1164                   GtkTreeIter  * iter,
1165                   gpointer       user_data UNUSED )
1166{
1167    TrTorrent * gtor = NULL;
1168    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1169    tr_torrent_open_folder( gtor );
1170    g_object_unref( G_OBJECT( gtor ) );
1171}
1172
1173static void
1174showInfoForeach (GtkTreeModel * model,
1175                 GtkTreePath  * path UNUSED,
1176                 GtkTreeIter  * iter,
1177                 gpointer       user_data )
1178{
1179    const char * hashString;
1180    struct cbdata * data = user_data;
1181    TrTorrent * tor = NULL;
1182    GtkWidget * w;
1183
1184    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
1185    hashString = tr_torrent_info(tor)->hashString;
1186    w = g_hash_table_lookup( data->tor2details, hashString );
1187    if( w != NULL )
1188        gtk_window_present( GTK_WINDOW( w ) );
1189    else {
1190        w = torrent_inspector_new( GTK_WINDOW( data->wind ), tor );
1191        gtk_widget_show( w );
1192        g_hash_table_insert( data->tor2details, (gpointer)hashString, w );
1193        g_hash_table_insert( data->details2tor, w, (gpointer)hashString );
1194        g_object_weak_ref( G_OBJECT( w ), detailsClosed, data );
1195    }
1196
1197    g_object_unref( G_OBJECT( tor ) );
1198}
1199
1200static void
1201recheckTorrentForeach (GtkTreeModel * model,
1202                       GtkTreePath  * path UNUSED,
1203                       GtkTreeIter  * iter,
1204                       gpointer       data UNUSED)
1205{
1206    TrTorrent * gtor = NULL;
1207    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1208    tr_torrentVerify( tr_torrent_handle( gtor ) );
1209    g_object_unref( G_OBJECT( gtor ) );
1210}
1211
1212static gboolean
1213msgwinclosed( void )
1214{
1215  action_toggle( "toggle-message-log", FALSE );
1216  return FALSE;
1217}
1218
1219static void
1220accumulateSelectedTorrents( GtkTreeModel * model,
1221                            GtkTreePath  * path UNUSED,
1222                            GtkTreeIter  * iter,
1223                            gpointer       gdata )
1224{
1225    GSList ** data = ( GSList** ) gdata;
1226    TrTorrent * tor = NULL;
1227    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
1228    *data = g_slist_prepend( *data, tor );
1229}
1230
1231static void
1232removeSelected( struct cbdata * data, gboolean delete_files )
1233{
1234    GSList * l = NULL;
1235    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1236    gtk_tree_selection_selected_foreach( s, accumulateSelectedTorrents, &l );
1237    gtk_tree_selection_unselect_all( s );
1238    if( l ) {
1239        l = g_slist_reverse( l );
1240        confirmRemove( data->wind, data->core, l, delete_files );
1241    }
1242}
1243
1244void
1245doAction ( const char * action_name, gpointer user_data )
1246{
1247    struct cbdata * data = user_data;
1248    gboolean changed = FALSE;
1249
1250    if ( !strcmp (action_name, "add-torrent-menu") ||
1251         !strcmp( action_name, "add-torrent-toolbar" ))
1252    {
1253        addDialog( data->wind, data->core );
1254    }
1255    else if (!strcmp (action_name, "show-stats"))
1256    {
1257        GtkWidget * dialog = stats_dialog_create( data->wind,
1258                                                  data->core );
1259        gtk_widget_show( dialog );
1260    }
1261    else if (!strcmp (action_name, "start-torrent"))
1262    {
1263        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1264        gtk_tree_selection_selected_foreach( s, startTorrentForeach, NULL );
1265        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1266    }
1267    else if (!strcmp (action_name, "pause-torrent"))
1268    {
1269        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1270        gtk_tree_selection_selected_foreach( s, stopTorrentForeach, NULL );
1271        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1272    }
1273    else if (!strcmp (action_name, "verify-torrent"))
1274    {
1275        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1276        gtk_tree_selection_selected_foreach( s, recheckTorrentForeach, NULL );
1277        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1278    }
1279    else if (!strcmp (action_name, "open-torrent-folder"))
1280    {
1281        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1282        gtk_tree_selection_selected_foreach( s, openFolderForeach, data );
1283    }
1284    else if (!strcmp (action_name, "show-torrent-details"))
1285    {
1286        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1287        gtk_tree_selection_selected_foreach( s, showInfoForeach, data );
1288    }
1289    else if (!strcmp( action_name, "update-tracker"))
1290    {
1291        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1292        gtk_tree_selection_selected_foreach( s, updateTrackerForeach, data->wind );
1293    }
1294    else if (!strcmp (action_name, "new-torrent"))
1295    {
1296        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ),
1297                                      tr_core_handle( data->core ) );
1298        gtk_widget_show_all( w );
1299    }
1300    else if( !strcmp( action_name, "remove-torrent" ) )
1301    {
1302        removeSelected( data, FALSE );
1303    }
1304    else if( !strcmp( action_name, "delete-torrent" ) )
1305    {
1306        removeSelected( data, TRUE );
1307    }
1308    else if (!strcmp (action_name, "quit"))
1309    {
1310        askquit( data->core, data->wind, wannaquit, data );
1311    }
1312    else if (!strcmp (action_name, "select-all"))
1313    {
1314        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1315        gtk_tree_selection_select_all( s );
1316    }
1317    else if (!strcmp (action_name, "deselect-all"))
1318    {
1319        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1320        gtk_tree_selection_unselect_all( s );
1321    }
1322    else if (!strcmp (action_name, "edit-preferences"))
1323    {
1324        if( NULL == data->prefs )
1325        {
1326            data->prefs = tr_prefs_dialog_new( G_OBJECT(data->core), data->wind );
1327            g_signal_connect( data->prefs, "destroy",
1328                             G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1329            gtk_widget_show( GTK_WIDGET( data->prefs ) );
1330        }
1331    }
1332    else if (!strcmp (action_name, "toggle-message-log"))
1333    {
1334        if( !data->msgwin )
1335        {
1336            GtkWidget * win = msgwin_new( data->core );
1337            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ), 
1338                             NULL );
1339            data->msgwin = win;
1340        }
1341        else
1342        {
1343            action_toggle("toggle-message-log", FALSE);
1344            gtk_widget_destroy( data->msgwin );
1345            data->msgwin = NULL;
1346        }
1347    }
1348    else if (!strcmp (action_name, "show-about-dialog"))
1349    {
1350        about( data->wind );
1351    }
1352    else if (!strcmp (action_name, "help"))
1353    {
1354        char * url = gtr_get_help_url( );
1355        gtr_open_file( url );
1356        g_free( url );
1357    }
1358    else if (!strcmp (action_name, "toggle-main-window"))
1359    {
1360        toggleMainWindow( data );
1361    }
1362    else g_error ("Unhandled action: %s", action_name );
1363
1364    if( changed )
1365        updatemodel( data );
1366}
Note: See TracBrowser for help on using the repository browser.