source: trunk/gtk/main.c @ 6162

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

unify the daemon and gtk client's config files so that you can easily swap back and forth between clients and keep the same torrents and preferences.

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