source: trunk/gtk/main.c @ 6413

Last change on this file since 6413 was 6413, checked in by charles, 14 years ago

(gtk) fix build breakage by including <locale.h> explicitly

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