source: trunk/gtk/main.c @ 6118

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

(gtk) #1012: don't show the tray by default

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