source: trunk/gtk/tr_window.c @ 1515

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

Spruce up the menu a bit and add it to a menubar and the tray icon.

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