source: trunk/gtk/tr_window.c @ 1647

Last change on this file since 1647 was 1647, checked in by joshe, 15 years ago

Make evil initial window sizing magic a bit less evil and a bit less magic.

  • Property svn:keywords set to Date Rev Author Id
File size: 24.8 KB
Line 
1/******************************************************************************
2 * $Id: tr_window.c 1647 2007-04-03 08:18:53Z joshe $
3 *
4 * Copyright (c) 2005-2007 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 <string.h>
26
27#include <gtk/gtk.h>
28#include <glib/gi18n.h>
29
30#include "transmission.h"
31
32#include "tr_cell_renderer_progress.h"
33#include "tr_torrent.h"
34#include "tr_window.h"
35#include "util.h"
36
37#define ITEM_ACTION             "tr-window-item-action"
38
39enum
40{
41    PROP_MODEL = 1,
42    PROP_SELECTION,
43    PROP_DOUBLECLICK,
44    PROP_DRAG,
45};
46
47static void
48tr_window_init( GTypeInstance * instance, gpointer g_class );
49static void
50tr_window_set_property( GObject * object, guint property_id,
51                        const GValue * value, GParamSpec * pspec );
52static void
53tr_window_get_property( GObject * object, guint property_id,
54                        GValue * value, GParamSpec * pspec);
55static void
56tr_window_class_init( gpointer g_class, gpointer g_class_data );
57static void
58tr_window_dispose( GObject * obj );
59static GtkTreeView *
60makeview( TrWindow * self );
61static void
62stylekludge( GObject * obj, GParamSpec * spec, gpointer data );
63static void
64fixbuttons( GtkTreeSelection *sel, TrWindow * self );
65static void
66formatname( GtkTreeViewColumn * col, GtkCellRenderer * rend,
67            GtkTreeModel * model, GtkTreeIter * iter, gpointer data );
68static void
69formatprog( GtkTreeViewColumn * col, GtkCellRenderer * rend,
70            GtkTreeModel * model, GtkTreeIter * iter, gpointer data );
71static gboolean
72listclick( GtkWidget * view, GdkEventButton * event, gpointer data );
73static gboolean
74listpopup( GtkWidget * view SHUTUP, gpointer data );
75static void
76popupmenu( TrWindow * self, GdkEventButton * event );
77static void
78itemclick( GObject * obj, gpointer data );
79static void
80doubleclick( GtkWidget * view, GtkTreePath * path,
81             GtkTreeViewColumn * col SHUTUP, gpointer data );
82static void
83emitaction( TrWindow * self, int id );
84static void
85orstatus( GtkTreeModel * model, GtkTreePath * path SHUTUP, GtkTreeIter * iter,
86          gpointer data );
87static void
88istorsel( GtkTreeModel * model, GtkTreePath * path SHUTUP, GtkTreeIter * iter,
89          gpointer data );
90
91GType
92tr_window_get_type( void )
93{
94    static GType type = 0;
95
96    if( 0 == type )
97    {
98        static const GTypeInfo info =
99        {
100            sizeof( TrWindowClass ),
101            NULL,                       /* base_init */
102            NULL,                       /* base_finalize */
103            tr_window_class_init,       /* class_init */
104            NULL,                       /* class_finalize */
105            NULL,                       /* class_data */
106            sizeof( TrWindow ),
107            0,                          /* n_preallocs */
108            tr_window_init,             /* instance_init */
109            NULL,
110        };
111        type = g_type_register_static( GTK_TYPE_WINDOW, "TrWindow", &info, 0 );
112    }
113
114    return type;
115}
116
117static void
118tr_window_class_init( gpointer g_class, gpointer g_class_data SHUTUP )
119{
120    GObjectClass  * gobject_class;
121    TrWindowClass * trwindow_class;
122    GParamSpec   * pspec;
123
124    gobject_class = G_OBJECT_CLASS( g_class );
125    gobject_class->set_property = tr_window_set_property;
126    gobject_class->get_property = tr_window_get_property;
127    gobject_class->dispose      = tr_window_dispose;
128
129    pspec = g_param_spec_object( "model", _("Model"),
130                                 _("The GtkTreeModel for the list view."),
131                                 GTK_TYPE_TREE_MODEL, G_PARAM_READWRITE );
132    g_object_class_install_property( gobject_class, PROP_MODEL, pspec );
133
134    pspec = g_param_spec_object( "selection", _("Selection"),
135                                 _("The GtkTreeSelection for the list view."),
136                                 GTK_TYPE_TREE_SELECTION, G_PARAM_READABLE );
137    g_object_class_install_property( gobject_class, PROP_SELECTION, pspec );
138
139    pspec = g_param_spec_int( "double-click-action", _("Double-click action"),
140                              _("The action id to signal on a double click."),
141                              G_MININT, G_MAXINT, -1, G_PARAM_READWRITE );
142    g_object_class_install_property( gobject_class, PROP_DOUBLECLICK, pspec );
143
144    pspec = g_param_spec_object( "drag-widget", _("Drag widget"),
145                                 _("The GtkWidget used for drag-and-drop."),
146                                 GTK_TYPE_WIDGET, G_PARAM_READABLE );
147    g_object_class_install_property( gobject_class, PROP_DRAG, pspec );
148
149    trwindow_class = TR_WINDOW_CLASS( g_class );
150    trwindow_class->actionsig =
151        g_signal_new( "action", G_TYPE_FROM_CLASS( g_class ),
152                       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
153                       g_cclosure_marshal_VOID__INT,
154                       G_TYPE_NONE, 1, G_TYPE_INT );
155}
156
157static void
158tr_window_init( GTypeInstance * instance, gpointer g_class SHUTUP )
159{
160    TrWindow * self = ( TrWindow * )instance;
161    GtkWidget * vbox, * scroll, * status, * tools, * menu, * file, * item;
162
163    vbox   = gtk_vbox_new( FALSE, 0 );
164    scroll = gtk_scrolled_window_new( NULL, NULL );
165    status = gtk_statusbar_new();
166    tools  = gtk_toolbar_new();
167    menu   = gtk_menu_bar_new();
168    file   = gtk_menu_new();
169    item   = gtk_menu_item_new_with_mnemonic( _("_File") );
170
171    self->scroll          = GTK_SCROLLED_WINDOW( scroll );
172    self->view            = makeview( self );
173    self->status          = GTK_STATUSBAR( status );
174    self->toolbar         = GTK_TOOLBAR( tools );
175    self->menu            = GTK_MENU_SHELL( file );
176    /* this should have been set by makeview() */
177    g_assert( NULL != self->namerend );
178    self->doubleclick     = -1;
179    self->actions         = NULL;
180    self->accel           = gtk_accel_group_new();
181    self->stupidpopuphack = NULL;
182    self->disposed        = FALSE;
183
184    gtk_window_add_accel_group( GTK_WINDOW( self ), self->accel );
185    gtk_menu_set_accel_group( GTK_MENU( self->menu ), self->accel );
186
187    gtk_menu_item_set_submenu( GTK_MENU_ITEM( item ), file );
188    gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
189    gtk_box_pack_start( GTK_BOX( vbox ), menu, FALSE, FALSE, 0 );
190
191    gtk_toolbar_set_tooltips( self->toolbar, TRUE );
192    gtk_toolbar_set_show_arrow( self->toolbar, FALSE );
193    gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( self->toolbar ),
194                        FALSE, FALSE, 0 );
195
196    gtk_container_add( GTK_CONTAINER( scroll ), GTK_WIDGET( self->view ) );
197    gtk_box_pack_start( GTK_BOX( vbox ), scroll, TRUE, TRUE, 0 );
198
199    gtk_statusbar_push( self->status, 0, "" );
200    gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( self->status ),
201                        FALSE, FALSE, 0 );
202
203    gtk_container_set_focus_child( GTK_CONTAINER( vbox ), scroll );
204    gtk_widget_show_all( vbox );
205    gtk_container_add( GTK_CONTAINER( self ), vbox );
206    gtk_window_set_title( GTK_WINDOW( self ), g_get_application_name());
207    gtk_window_set_role( GTK_WINDOW( self ), "tr-main" );
208}
209
210static void
211tr_window_set_property( GObject * object, guint property_id,
212                        const GValue * value SHUTUP, GParamSpec * pspec)
213{
214    TrWindow         * self = ( TrWindow * )object;
215
216    if( self->disposed )
217    {
218        return;
219    }
220
221    switch( property_id )
222    {
223        case PROP_MODEL:
224            gtk_tree_view_set_model( self->view, g_value_get_object( value ) );
225            break;
226        case PROP_DOUBLECLICK:
227            self->doubleclick = g_value_get_int( value );
228            break;
229        default:
230            G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
231            break;
232    }
233}
234
235static void
236tr_window_get_property( GObject * object, guint property_id,
237                        GValue * value SHUTUP, GParamSpec * pspec )
238{
239    TrWindow         * self = ( TrWindow * )object;
240
241    if( self->disposed )
242    {
243        return;
244    }
245
246    switch( property_id )
247    {
248        case PROP_MODEL:
249            g_value_set_object( value, gtk_tree_view_get_model( self->view ) );
250            break;
251        case PROP_SELECTION:
252            g_value_set_object( value,
253                                gtk_tree_view_get_selection( self->view ) );
254            break;
255        case PROP_DOUBLECLICK:
256            g_value_set_int( value, self->doubleclick );
257            break;
258        case PROP_DRAG:
259            g_value_set_object( value, self->view );
260            break;
261        default:
262            G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
263            break;
264    }
265}
266
267static void
268tr_window_dispose( GObject * obj )
269{
270    TrWindow     * self = ( TrWindow * )obj;
271    GObjectClass * parent;
272
273    if( self->disposed )
274    {
275        return;
276    }
277    self->disposed = TRUE;
278
279    g_list_foreach( self->actions, ( GFunc )action_free, NULL );
280    g_list_free( self->actions );
281    g_object_unref( self->accel );
282    if( NULL != self->stupidpopuphack )
283    {
284        gtk_widget_destroy( self->stupidpopuphack );
285    }
286    self->stupidpopuphack = NULL;
287
288    /* Chain up to the parent class */
289    parent = g_type_class_peek( g_type_parent( TR_WINDOW_TYPE ) );
290    parent->dispose( obj );
291}
292
293GtkWidget *
294tr_window_new( void )
295{
296    return g_object_new( TR_WINDOW_TYPE, NULL );
297}
298
299void
300tr_window_action_add( TrWindow * self, int id, int flags, const char * name,
301                      const char * icon, const char * description, guint key )
302{
303    struct action * act;
304    GtkWidget     * sep;
305
306    TR_IS_WINDOW( self );
307    if( self->disposed )
308    {
309        return;
310    }
311
312    act = action_new( id, flags, name, icon );
313
314    if( ACTF_TOOL & flags )
315    {
316        act->tool = action_maketool( act, ITEM_ACTION,
317                                     G_CALLBACK( itemclick ), self );
318        gtk_tool_item_set_tooltip( GTK_TOOL_ITEM( act->tool ),
319                                   self->toolbar->tooltips, description, "" );
320        gtk_toolbar_insert( self->toolbar, GTK_TOOL_ITEM( act->tool ), -1 );
321    }
322
323    if( ACTF_MENU & flags )
324    {
325        act->menu = action_makemenu( act, ITEM_ACTION, self->accel,
326                                     "<transmission-mainwind>/file", key,
327                                     G_CALLBACK( itemclick ), self );
328        gtk_menu_shell_append( self->menu, act->menu );
329    }
330
331    if( ACTF_SEPARATOR & flags )
332    {
333        sep = gtk_separator_menu_item_new();
334        gtk_widget_show( sep );
335        gtk_menu_shell_append( self->menu, sep );
336    }
337
338    self->actions = g_list_append( self->actions, act );
339}
340
341void
342tr_window_update( TrWindow * self, float downspeed, float upspeed )
343{
344    char * downstr, * upstr, * str;
345
346    TR_IS_WINDOW( self );
347    if( self->disposed )
348    {
349        return;
350    }
351
352    /* update the status bar */
353    downstr = readablesize( downspeed * 1024.0 );
354    upstr   = readablesize( upspeed * 1024.0 );
355    str     = g_strdup_printf( _("     Total DL: %s/s     Total UL: %s/s"),
356                               downstr, upstr );
357    g_free( downstr );
358    g_free( upstr );
359    gtk_statusbar_pop( self->status, 0 );
360    gtk_statusbar_push( self->status, 0, str );
361    g_free( str );
362
363    /* the selection's status may have changed so update the buttons */
364    fixbuttons( NULL, self );
365}
366
367void
368tr_window_show( TrWindow * self )
369{
370    TR_IS_WINDOW( self );
371
372    sizingmagic( GTK_WINDOW( self ), self->scroll,
373                 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS );
374    g_object_set( self->namerend, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
375    gtk_widget_show( GTK_WIDGET( self ) );
376}
377
378static GtkTreeView *
379makeview( TrWindow * self )
380{
381    GtkWidget         * view;
382    GtkTreeViewColumn * col;
383    GtkTreeSelection  * sel;
384    GtkCellRenderer   * namerend, * progrend;
385    char              * str;
386
387    TR_IS_WINDOW( self );
388
389    view     = gtk_tree_view_new();
390    namerend = gtk_cell_renderer_text_new();
391    self->namerend = G_OBJECT( namerend );
392    /* note that this renderer is set to ellipsize, just not here */
393    col = gtk_tree_view_column_new_with_attributes( _("Name"), namerend,
394                                                    NULL );
395    gtk_tree_view_column_set_cell_data_func( col, namerend, formatname,
396                                             NULL, NULL );
397    gtk_tree_view_column_set_expand( col, TRUE );
398    gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_AUTOSIZE );
399    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
400
401    progrend = tr_cell_renderer_progress_new();
402    /* this string is only used to determine the size of the progress bar */
403    str = g_markup_printf_escaped( "<big>%s</big>", _("  fnord    fnord  ") );
404    g_object_set( progrend, "bar-sizing", str, NULL );
405    g_free(str);
406    col = gtk_tree_view_column_new_with_attributes( _("Progress"), progrend,
407                                                    NULL);
408    gtk_tree_view_column_set_cell_data_func( col, progrend, formatprog,
409                                             NULL, NULL );
410    gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_AUTOSIZE );
411    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
412
413    /* XXX this shouldn't be necessary */
414    g_signal_connect( view, "notify::style",
415                      G_CALLBACK( stylekludge ), progrend );
416
417    gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( view ), TRUE );
418    sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
419    gtk_tree_selection_set_mode( GTK_TREE_SELECTION( sel ),
420                                 GTK_SELECTION_MULTIPLE );
421
422    g_signal_connect( G_OBJECT( sel ), "changed",
423                      G_CALLBACK( fixbuttons ), self );
424    g_signal_connect( G_OBJECT( view ), "button-press-event",
425                      G_CALLBACK( listclick ), self );
426    g_signal_connect( G_OBJECT( view ), "popup-menu",
427                      G_CALLBACK( listpopup ), self );
428    g_signal_connect( G_OBJECT( view ), "row-activated",
429                      G_CALLBACK( doubleclick ), self );
430
431    return GTK_TREE_VIEW( view );
432}
433
434/* kludge to have the progress bars notice theme changes */
435static void
436stylekludge( GObject * obj, GParamSpec * spec, gpointer data )
437{
438    if( 0 == strcmp( "style", spec->name ) )
439    {
440        tr_cell_renderer_progress_reset_style(
441            TR_CELL_RENDERER_PROGRESS( data ) );
442        gtk_widget_queue_draw( GTK_WIDGET( obj ) );
443    }
444}
445
446/* disable buttons and menuitems the user shouldn't be able to click on */
447static void
448fixbuttons( GtkTreeSelection *sel, TrWindow * self ) {
449    gboolean        selected, avail;
450    GList         * ii;
451    int             status;
452    struct action * act;
453
454    TR_IS_WINDOW( self );
455    if( self->disposed )
456    {
457        return;
458    }
459
460    if( NULL == sel )
461    {
462        sel = gtk_tree_view_get_selection( self->view );
463    }
464    status = 0;
465    gtk_tree_selection_selected_foreach( sel, orstatus, &status );
466    selected = ( 0 < gtk_tree_selection_count_selected_rows( sel ) );
467
468    for( ii = g_list_first( self->actions ); NULL != ii; ii = ii->next )
469    {
470        act = ii->data;
471        if( ACTF_ALWAYS & act->flags )
472        {
473            continue;
474        }
475        avail = ACT_ISAVAIL( act->flags, status );
476        if( ACTF_TOOL & act->flags )
477        {
478            g_assert( NULL != act->tool );
479            gtk_widget_set_sensitive( act->tool, selected && avail );
480        }
481        if( ACTF_MENU & act->flags )
482        {
483            g_assert( NULL != act->menu );
484            gtk_widget_set_sensitive( act->menu, selected && avail );
485        }
486    }
487}
488
489static void
490formatname( GtkTreeViewColumn * col SHUTUP, GtkCellRenderer * rend,
491            GtkTreeModel * model, GtkTreeIter * iter, gpointer data SHUTUP )
492{
493    char  * name, * mb, * terr, * str, * top, * bottom, * timestr;
494    guint64 size;
495    gfloat  prog;
496    int     status, err, eta, tpeers, upeers, dpeers;
497
498    gtk_tree_model_get( model, iter, MC_NAME, &name, MC_STAT, &status,
499                        MC_ERR, &err, MC_SIZE, &size, MC_PROG, &prog,
500                        MC_ETA, &eta, MC_PEERS, &tpeers, MC_UPEERS, &upeers,
501                        MC_DPEERS, &dpeers, -1 );
502
503    tpeers = MAX( tpeers, 0 );
504    upeers = MAX( upeers, 0 );
505    dpeers = MAX( dpeers, 0 );
506    mb = readablesize(size);
507    prog *= 100;
508
509    if( TR_STATUS_CHECK & status )
510    {
511        top = g_strdup_printf( _("Checking existing files (%.1f%%)"), prog );
512    }
513    else if( TR_STATUS_DOWNLOAD & status )
514    {
515        if( 0 > eta )
516        {
517            top = g_strdup_printf( _("Stalled (%.1f%%)"), prog );
518        }
519        else
520        {
521            timestr = readabletime(eta);
522            top = g_strdup_printf( _("Finishing in %s (%.1f%%)"),
523                                   timestr, prog );
524            g_free(timestr);
525        }
526    }
527    else if(TR_STATUS_SEED & status)
528    {
529        top = g_strdup_printf(
530            ngettext( "Seeding, uploading to %d of %d peer",
531                      "Seeding, uploading to %d of %d peers", tpeers ),
532            dpeers, tpeers );
533    }
534    else if( TR_STATUS_STOPPING & status )
535    {
536        top = g_strdup( _("Stopping...") );
537    }
538    else if( TR_STATUS_PAUSE & status )
539    {
540        top = g_strdup_printf( _("Stopped (%.1f%%)"), prog );
541    }
542    else
543    {
544        top = g_strdup( "" );
545        g_assert_not_reached();
546    }
547
548    if( TR_OK != err )
549    {
550        gtk_tree_model_get( model, iter, MC_TERR, &terr, -1 );
551        bottom = g_strconcat( _("Error: "), terr, NULL );
552        g_free( terr );
553    }
554    else if( TR_STATUS_DOWNLOAD & status )
555    {
556        bottom = g_strdup_printf( ngettext( "Downloading from %i of %i peer",
557                                            "Downloading from %i of %i peers",
558                                            tpeers ), upeers, tpeers );
559    }
560    else
561    {
562        bottom = NULL;
563    }
564
565    str = g_markup_printf_escaped( "<big>%s (%s)</big>\n<small>%s\n%s</small>",
566                                   name, mb, top,
567                                   ( NULL == bottom ? "" : bottom ) );
568    g_object_set( rend, "markup", str, NULL );
569    g_free( name );
570    g_free( mb );
571    g_free( str );
572    g_free( top );
573    g_free( bottom );
574}
575
576static void
577formatprog( GtkTreeViewColumn * col SHUTUP, GtkCellRenderer * rend,
578            GtkTreeModel * model, GtkTreeIter * iter, gpointer data SHUTUP )
579{
580    char  * dlstr, * ulstr, * str, * marked;
581    gfloat  prog, dl, ul;
582    guint64 down, up;
583
584    gtk_tree_model_get( model, iter, MC_PROG, &prog, MC_DRATE, &dl,
585                        MC_URATE, &ul, MC_DOWN, &down, MC_UP, &up, -1 );
586    prog = MAX( prog, 0.0 );
587    prog = MIN( prog, 1.0 );
588
589    ulstr = readablesize( ul * 1024.0 );
590    if( 1.0 == prog )
591    {
592        dlstr = ratiostr( down, up );
593        str = g_strdup_printf( _("Ratio: %s\nUL: %s/s"), dlstr, ulstr );
594    }
595    else
596    {
597        dlstr = readablesize( dl * 1024.0 );
598        str = g_strdup_printf( _("DL: %s/s\nUL: %s/s"), dlstr, ulstr );
599    }
600    marked = g_markup_printf_escaped( "<small>%s</small>", str );
601    g_object_set( rend, "markup", str, "progress", prog, NULL );
602    g_free( dlstr );
603    g_free( ulstr );
604    g_free( str );
605    g_free( marked );
606}
607
608/* show a popup menu for a right-click on the list */
609static gboolean
610listclick( GtkWidget * view, GdkEventButton * event, gpointer data )
611{
612    TrWindow         * self;
613    GtkTreeSelection * sel;
614    GtkTreePath      * path;
615    GtkTreeModel     * model;
616    GtkTreeIter        iter;
617    int                status;
618    TrTorrent        * tor, * issel;
619
620    if( GDK_BUTTON_PRESS != event->type || 3 != event->button )
621    {
622        return FALSE;
623    }
624
625    TR_IS_WINDOW( data );
626    self = TR_WINDOW( data );
627    if( self->disposed )
628    {
629        return FALSE;
630    }
631
632    sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
633    model = gtk_tree_view_get_model( GTK_TREE_VIEW( view ) );
634
635    /* find what row, if any, the user clicked on */
636    if( gtk_tree_view_get_path_at_pos( GTK_TREE_VIEW( view ),
637                                        event->x, event->y, &path,
638                                        NULL, NULL, NULL ) )
639    {
640        if( gtk_tree_model_get_iter( model, &iter, path ) )
641        {
642            /* get torrent and status for the right-clicked row */
643            gtk_tree_model_get( model, &iter, MC_TORRENT, &tor,
644                                MC_STAT, &status, -1 );
645            issel = tor;
646            gtk_tree_selection_selected_foreach( sel, istorsel, &issel );
647            g_object_unref( tor );
648            /* if the clicked row isn't selected, select only it */
649            if( NULL != issel )
650            {
651                gtk_tree_selection_unselect_all( sel );
652                gtk_tree_selection_select_iter( sel, &iter );
653            }
654        }
655        gtk_tree_path_free( path );
656    }
657    else
658    {
659        gtk_tree_selection_unselect_all( sel );
660    }
661
662    popupmenu( self, event );
663
664    return TRUE;
665}
666
667static gboolean
668listpopup( GtkWidget * view SHUTUP, gpointer data )
669{
670    popupmenu( TR_WINDOW( data ), NULL );
671    return TRUE;
672}
673
674static void
675popupmenu( TrWindow * self, GdkEventButton * event )
676{
677    GtkTreeSelection * sel;
678    int                count, status;
679    GtkWidget        * menu, * item;
680    GList            * ii;
681    struct action    * act;
682
683    TR_IS_WINDOW( self );
684    if( self->disposed )
685    {
686        return;
687    }
688
689    sel   = gtk_tree_view_get_selection( self->view );
690    count = gtk_tree_selection_count_selected_rows( sel );
691    menu  = gtk_menu_new();
692
693    if( NULL != self->stupidpopuphack )
694    {
695        gtk_widget_destroy( self->stupidpopuphack );
696    }
697    self->stupidpopuphack = menu;
698
699    status = 0;
700    gtk_tree_selection_selected_foreach( sel, orstatus, &status );
701
702    for( ii = g_list_first( self->actions ); NULL != ii; ii = ii->next )
703    {
704        act = ii->data;
705        if( ACTF_SEPARATOR & act->flags )
706        {
707            item = gtk_separator_menu_item_new();
708            gtk_widget_show( item );
709            gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
710        }
711        else if( ACTF_MENU & act->flags && ACT_ISAVAIL( act->flags, status ) )
712        {
713            item = action_makemenu( act, ITEM_ACTION, NULL, NULL, 0,
714                                    G_CALLBACK( itemclick ), self );
715            gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
716        }
717    }
718
719    gtk_widget_show( menu );
720
721    gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL,
722                    ( NULL == event ? 0 : event->button ),
723                    gdk_event_get_time( (GdkEvent*)event ) );
724}
725
726static void
727itemclick( GObject * obj, gpointer data )
728{
729    TrWindow      * self;
730    struct action * act;
731
732    TR_IS_WINDOW( data );
733    self = TR_WINDOW( data );
734    act = g_object_get_data( obj, ITEM_ACTION );
735
736    emitaction( self, act->id );
737}
738
739static void
740doubleclick( GtkWidget * view SHUTUP, GtkTreePath * path,
741             GtkTreeViewColumn * col SHUTUP, gpointer data )
742{
743    TrWindow         * self;
744    GtkTreeSelection * sel;
745
746    TR_IS_WINDOW( data );
747    self = TR_WINDOW( data );
748    if( self->disposed || 0 > self->doubleclick )
749    {
750        return;
751    }
752
753    sel = gtk_tree_view_get_selection( self->view );
754    gtk_tree_selection_select_path( sel, path );
755
756    emitaction( self, self->doubleclick );
757}
758
759static void
760emitaction( TrWindow * self, int id )
761{
762    TrWindowClass * class;
763
764    TR_IS_WINDOW( self );
765    if( self->disposed )
766    {
767        return;
768    }
769
770    class = g_type_class_peek( TR_WINDOW_TYPE );
771    g_signal_emit( self, class->actionsig, 0, id );
772}
773
774/* use with gtk_tree_selection_selected_foreach to | status of selected rows */
775static void
776orstatus( GtkTreeModel * model, GtkTreePath * path SHUTUP, GtkTreeIter * iter,
777          gpointer data )
778{
779    int * allstatus, thisstatus;
780
781  allstatus = data;
782  gtk_tree_model_get( model, iter, MC_STAT, &thisstatus, -1 );
783  *allstatus |= thisstatus;
784}
785
786/* data should be a TrTorrent**, will set torrent to NULL if it's selected */
787static void
788istorsel( GtkTreeModel * model, GtkTreePath * path SHUTUP, GtkTreeIter * iter,
789          gpointer data )
790{
791    TrTorrent ** torref, * tor;
792
793    torref = data;
794    if( NULL != *torref )
795    {
796        gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
797        if( tor == *torref )
798        {
799            *torref = NULL;
800        }
801        g_object_unref( tor );
802    }
803}
Note: See TracBrowser for help on using the repository browser.