source: trunk/gtk/main.c @ 6559

Last change on this file since 6559 was 6559, checked in by muks, 13 years ago

Don't show an error when transmission is run twice

Instead, present the main window. This commit also auto-generates
the dbus bindings.

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