source: trunk/gtk/main.c @ 6561

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

Check for threads

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