source: trunk/gtk/details.c @ 6115

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

more webseed work.

  • Property svn:keywords set to Date Rev Author Id
File size: 43.8 KB
Line 
1/*
2 * This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: details.c 6115 2008-06-10 02:36:52Z charles $
11 */
12
13#include <errno.h>
14#include <stddef.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <glib/gi18n.h>
18#include <gtk/gtk.h>
19
20#include <libtransmission/transmission.h>
21#include <libtransmission/utils.h> /* tr_httpIsValidURL */
22
23#include "actions.h"
24#include "details.h"
25#include "file-list.h"
26#include "tr-torrent.h"
27#include "tracker-list.h"
28#include "hig.h"
29#include "util.h"
30
31#define UPDATE_INTERVAL_MSEC 2000
32
33/****
34*****  PIECES VIEW
35****/
36
37/* define SHOW_PIECES */
38
39#ifdef SHOW_PIECES
40static int
41getGridSize (int pieceCount, int * n_rows, int * n_cols)
42{
43  const int MAX_ACROSS = 16;
44  if (pieceCount >= (MAX_ACROSS * MAX_ACROSS)) {
45    *n_rows = *n_cols = MAX_ACROSS;
46    return MAX_ACROSS * MAX_ACROSS;
47  }
48  else {
49    int i;
50    for (i=0; (i*i) < pieceCount; ++i);
51    *n_rows = *n_cols = i;
52    return pieceCount;
53  }
54}
55
56#define TO16(a) ((guint16)((a<<8)|(a)))
57#define RGB_2_GDK(R,G,B) { 0, TO16(R), TO16(G), TO16(B) }
58
59enum { DRAW_AVAIL, DRAW_PROG };
60
61static void
62release_gobject_array (gpointer data)
63{
64  int i;
65  GObject **objects = (GObject**) data;
66  for (i=0; objects[i]!=NULL; ++i)
67    g_object_unref (G_OBJECT(objects[i]));
68  g_free (objects);
69}
70
71static gboolean
72refresh_pieces( GtkWidget * da, GdkEventExpose * event UNUSED, gpointer gtor )
73{
74  tr_torrent * tor = tr_torrent_handle( TR_TORRENT(gtor) );
75  const tr_info * info = tr_torrent_info( TR_TORRENT(gtor) );
76  int mode = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(da), "draw-mode"));
77
78  GdkColormap * colormap = gtk_widget_get_colormap (da);
79  const int widget_w = da->allocation.width;
80  const int widget_h = da->allocation.height;
81  int n_rows, n_cols;
82  const int n_cells = getGridSize (info->pieceCount,  &n_rows, &n_cols);
83  const GdkRectangle grid_bounds = { 0, 0, widget_w, widget_h };
84  const double piece_w = grid_bounds.width / (double)n_cols;
85  const double piece_h = grid_bounds.height / (double)n_rows;
86  const int piece_w_int = (int) (piece_w + 1); /* pad for roundoff */
87  const int piece_h_int = (int) (piece_h + 1); /* pad for roundoff */
88  const gboolean rtl = gtk_widget_get_direction( da ) == GTK_TEXT_DIR_RTL;
89
90  guint8 * prev_color = NULL;
91  gboolean first_time = FALSE;
92
93  int i, x, y;
94  int8_t * pieces = NULL;
95  float * completeness = NULL;
96
97  /**
98  ***  Get the Graphics Contexts...
99  **/
100
101  enum { ALL, LOTS, SOME, FEW, NONE,
102         BLACK, GRAY, BLINK,
103         N_COLORS };
104  GdkGC **gcs = (GdkGC**) g_object_get_data (G_OBJECT(da), "graphics-contexts");
105  if (gcs == NULL)
106  {
107    const GdkColor colors [N_COLORS] = {
108      RGB_2_GDK ( 114, 159, 207 ), /* all */
109      RGB_2_GDK (  52, 101, 164 ), /* lots */
110      RGB_2_GDK (  32,  74, 135 ), /* some */
111      RGB_2_GDK (  85,  87,  83 ), /* few */
112      RGB_2_GDK ( 238, 238, 236 ), /* none - tango aluminum highlight */
113      RGB_2_GDK (  46,  52,  54 ), /* black - tango slate shadow */
114      RGB_2_GDK ( 186, 189, 182 ), /* gray - tango aluminum shadow */
115      RGB_2_GDK ( 252, 233,  79 ), /* blink - tango butter highlight */
116    };
117
118    gcs = g_new (GdkGC*, N_COLORS+1);
119
120    for (i=0; i<N_COLORS; ++i) {
121      gcs[i] = gdk_gc_new (da->window);
122      gdk_gc_set_colormap (gcs[i], colormap);
123      gdk_gc_set_rgb_fg_color (gcs[i], &colors[i]);
124      gdk_gc_set_rgb_bg_color (gcs[i], &colors[i]);
125    };
126
127    gcs[N_COLORS] = NULL; /* a sentinel in the release function */
128    g_object_set_data_full (G_OBJECT(da), "graphics-contexts",
129                            gcs, release_gobject_array);
130  }
131
132  /**
133  ***  Get the cells' previous colors...
134  ***  (this is used for blinking when the color changes)
135  **/
136
137  prev_color = (guint8*) g_object_get_data (G_OBJECT(da), "prev-color");
138  if (prev_color == NULL)
139  {
140    first_time = TRUE;
141    prev_color = g_new0 (guint8, n_cells);
142    g_object_set_data_full (G_OBJECT(da), "prev-color", prev_color, g_free);
143  }
144
145  /**
146  ***  Get the piece data values...
147  **/
148
149  switch (mode) {
150    case DRAW_AVAIL:
151      pieces = g_new (int8_t, n_cells);
152      tr_torrentAvailability ( tor, pieces, n_cells );
153      break;
154    case DRAW_PROG:
155      completeness = g_new (float, n_cells);
156      tr_torrentAmountFinished ( tor, completeness, n_cells );
157      break;
158    default: g_error("no mode defined!");
159  }
160
161  /**
162  ***  Draw...
163  **/
164
165  i = 0; 
166  for (y=0; y<n_rows; ++y) {
167    for (x=0; x<n_cols; ++x) {
168      int draw_x = grid_bounds.x + (int)(x * piece_w);
169      int draw_y = grid_bounds.y + (int)(y * piece_h);
170      int color = BLACK;
171      int border = BLACK;
172
173      if( rtl )
174          draw_x = grid_bounds.x + grid_bounds.width - (int)((x+1) * piece_w);
175      else
176          draw_x = grid_bounds.x + (int)(x * piece_w);
177      draw_y = grid_bounds.y + (int)(y * piece_h);
178
179      if (i < n_cells)
180      {
181        border = GRAY;
182
183        if (mode == DRAW_AVAIL) {
184          const int8_t val = pieces[i];
185               if (val <  0) color = ALL;
186          else if (val == 0) color = NONE;
187          else if (val <= 4) color = FEW;
188          else if (val <= 8) color = SOME;
189          else               color = LOTS;
190        } else { /* completeness */
191          const float val = completeness[i];
192               if (val >= 1.00) color = ALL;
193          else if (val >= 0.66) color = LOTS;
194          else if (val >= 0.33) color = SOME;
195          else if (val >= 0.01) color = FEW;
196          else                  color = NONE;
197        }
198
199        /* draw a "blink" for one interval when a piece changes */
200        if (first_time)
201          prev_color[i] = color;
202        else if (color != prev_color[i]) {
203          prev_color[i] = color;
204          color = border = BLINK;
205        }
206      }
207
208      gdk_draw_rectangle (da->window, gcs[color], TRUE,
209                          draw_x, draw_y,
210                          piece_w_int, piece_h_int);
211
212      if (i < n_cells)
213        gdk_draw_rectangle (da->window, gcs[border], FALSE,
214                            draw_x, draw_y,
215                            piece_w_int, piece_h_int);
216
217      ++i;
218    }
219  }
220
221  gdk_draw_rectangle (da->window, gcs[GRAY], FALSE,
222                      grid_bounds.x, grid_bounds.y, 
223                      grid_bounds.width-1, grid_bounds.height-1);
224
225  g_free (pieces);
226  g_free (completeness);
227  return FALSE;
228}
229#endif
230
231/****
232*****  PEERS TAB
233****/
234
235enum
236{
237  WEBSEED_COL_URL,
238  WEBSEED_COL_DOWNLOAD_RATE,
239  N_WEBSEED_COLS
240};
241
242static const char * webseed_column_names[N_WEBSEED_COLS] =
243{
244    N_( "Web Seeds" ),
245    /* 'download speed' column header. terse to keep the column narrow. */
246    N_( "Down" )
247};
248
249static GtkTreeModel*
250webseed_model_new( const tr_torrent * tor )
251{
252    int i;
253    const tr_info * inf = tr_torrentInfo( tor );
254    float * speeds = tr_torrentWebSpeeds( tor );
255    GtkListStore * store = gtk_list_store_new( N_WEBSEED_COLS,
256                                               G_TYPE_STRING,
257                                               G_TYPE_FLOAT );
258    for( i=0; i<inf->webseedCount; ++i )
259    {
260        GtkTreeIter iter;
261        gtk_list_store_append( store, &iter );
262        gtk_list_store_set( store, &iter, WEBSEED_COL_URL, inf->webseeds[i],
263                                          WEBSEED_COL_DOWNLOAD_RATE, speeds[i],
264                                          -1 );
265    }
266
267    tr_free( speeds );
268    return GTK_TREE_MODEL( store );
269}
270
271enum
272{
273  PEER_COL_ADDRESS,
274  PEER_COL_DOWNLOAD_RATE,
275  PEER_COL_UPLOAD_RATE,
276  PEER_COL_CLIENT,
277  PEER_COL_PROGRESS,
278  PEER_COL_IS_ENCRYPTED,
279  PEER_COL_STATUS,
280  N_PEER_COLS
281};
282
283static const char* peer_column_names[N_PEER_COLS] =
284{
285  N_("Address"),
286  N_("Client"),
287  /* 'percent done' column header. terse to keep the column narrow. */
288  N_("%"),
289  " ",
290  /* 'download speed' column header. terse to keep the column narrow. */
291  N_("Down"),
292  /* 'upload speed' column header.  terse to keep the column narrow. */
293  N_("Up"),
294  N_("Status")
295};
296
297static int compare_peers (const void * a, const void * b)
298{
299  const tr_peer_stat * pa = a;
300  const tr_peer_stat * pb = b;
301  return strcmp (pa->addr, pb->addr);
302}
303static int compare_addr_to_peer (const void * a, const void * b)
304{
305  const char * addr = (const char *) a;
306  const tr_peer_stat * peer = b;
307  return strcmp (addr, peer->addr);
308}
309
310static void
311peer_row_set (GtkListStore        * store,
312              GtkTreeIter         * iter,
313              const tr_peer_stat  * peer)
314{
315  const char * client = peer->client;
316
317  if (!client || !strcmp(client,"Unknown Client"))
318    client = " ";
319
320  gtk_list_store_set( store, iter,
321                      PEER_COL_ADDRESS, peer->addr,
322                      PEER_COL_CLIENT, client,
323                      PEER_COL_IS_ENCRYPTED, peer->isEncrypted,
324                      PEER_COL_PROGRESS, (int)(100.0*peer->progress),
325                      PEER_COL_DOWNLOAD_RATE, peer->downloadFromRate,
326                      PEER_COL_UPLOAD_RATE, peer->uploadToRate,
327                      PEER_COL_STATUS, peer->flagStr,
328                      -1);
329}
330
331static void
332append_peers_to_model (GtkListStore        * store,
333                       const tr_peer_stat  * peers,
334                       int                   n_peers)
335{
336  int i;
337  for (i=0; i<n_peers; ++i) {
338    GtkTreeIter iter;
339    gtk_list_store_append( store, &iter );
340    peer_row_set (store, &iter, &peers[i]);
341  }
342}
343
344static GtkTreeModel*
345peer_model_new (tr_torrent * tor)
346{
347  GtkListStore * m = gtk_list_store_new (N_PEER_COLS,
348                                         G_TYPE_STRING,   /* addr */
349                                         G_TYPE_FLOAT,    /* downloadFromRate */
350                                         G_TYPE_FLOAT,    /* uploadToRate */
351                                         G_TYPE_STRING,   /* client */
352                                         G_TYPE_INT,      /* progress [0..100] */
353                                         G_TYPE_BOOLEAN,  /* isEncrypted */
354                                         G_TYPE_STRING ); /* flagString */
355
356  int n_peers = 0;
357  tr_peer_stat * peers = tr_torrentPeers (tor, &n_peers);
358  qsort (peers, n_peers, sizeof(tr_peer_stat), compare_peers);
359  append_peers_to_model (m, peers, n_peers);
360  tr_torrentPeersFree( peers, 0 );
361  return GTK_TREE_MODEL (m);
362}
363
364static void
365render_encrypted (GtkTreeViewColumn  * column UNUSED,
366                  GtkCellRenderer    * renderer,
367                  GtkTreeModel       * tree_model,
368                  GtkTreeIter        * iter,
369                  gpointer             data UNUSED)
370{
371  gboolean is_encrypted = FALSE;
372  gtk_tree_model_get (tree_model, iter, PEER_COL_IS_ENCRYPTED, &is_encrypted,
373                                        -1);
374  g_object_set (renderer, "xalign", (gfloat)0.0,
375                          "yalign", (gfloat)0.5,
376                          "stock-id", (is_encrypted ? "transmission-lock" : NULL),
377                          NULL);
378}
379
380static void
381render_ul_rate (GtkTreeViewColumn  * column UNUSED,
382                GtkCellRenderer    * renderer,
383                GtkTreeModel       * tree_model,
384                GtkTreeIter        * iter,
385                gpointer             data UNUSED)
386{
387  float rate = 0.0;
388  gtk_tree_model_get (tree_model, iter, PEER_COL_UPLOAD_RATE, &rate, -1);
389  if( rate < 0.01 )
390    g_object_set (renderer, "text", "", NULL);
391  else {
392    char speedStr[64];
393    tr_strlspeed( speedStr, rate, sizeof(speedStr) );
394    g_object_set( renderer, "text", speedStr, NULL );
395  }
396}
397
398static void
399render_dl_rate (GtkTreeViewColumn  * column UNUSED,
400                GtkCellRenderer    * renderer,
401                GtkTreeModel       * tree_model,
402                GtkTreeIter        * iter,
403                gpointer             data UNUSED)
404{
405  float rate = 0.0;
406  gtk_tree_model_get (tree_model, iter, PEER_COL_DOWNLOAD_RATE, &rate, -1);
407  if( rate < 0.01 )
408    g_object_set (renderer, "text", "", NULL);
409  else {
410    char speedStr[64];
411    tr_strlspeed( speedStr, rate, sizeof(speedStr) );
412    g_object_set( renderer, "text", speedStr, NULL );
413  }
414}
415
416static void
417render_client (GtkTreeViewColumn   * column UNUSED,
418               GtkCellRenderer     * renderer,
419               GtkTreeModel        * tree_model,
420               GtkTreeIter         * iter,
421               gpointer              data UNUSED)
422{
423  char * client = NULL;
424  gtk_tree_model_get (tree_model, iter, PEER_COL_CLIENT, &client,
425                                        -1);
426  g_object_set (renderer, "text", (client ? client : ""), NULL);
427  g_free (client);
428}
429
430typedef struct
431{
432  TrTorrent * gtor;
433  GtkTreeModel * model; /* same object as store, but recast */
434  GtkListStore * store; /* same object as model, but recast */
435  GtkListStore * webseeds;
436  GtkWidget * completeness;
437  GtkWidget * seeders_lb;
438  GtkWidget * leechers_lb;
439  GtkWidget * completed_lb;
440}
441PeerData;
442
443static void
444fmtpeercount (GtkWidget * l, int count)
445{
446  if( 0 > count ) {
447    gtk_label_set_text( GTK_LABEL(l), "?" );
448  } else {
449    char str[16];
450    g_snprintf( str, sizeof str, "%'d", count );
451    gtk_label_set_text( GTK_LABEL(l), str );
452  }
453}
454
455static void
456refresh_peers (GtkWidget * top)
457{
458  int i;
459  int n_peers;
460  GtkTreeIter iter;
461  PeerData * p = (PeerData*) g_object_get_data (G_OBJECT(top), "peer-data");
462  tr_torrent * tor = tr_torrent_handle ( p->gtor );
463  GtkTreeModel * model = p->model;
464  GtkListStore * store = p->store;
465  tr_peer_stat * peers;
466  const tr_stat * stat = tr_torrent_stat( p->gtor );
467  const tr_info * inf = tr_torrent_info( p->gtor );
468
469  if( inf->webseedCount )
470  {
471    float * speeds = tr_torrentWebSpeeds( tor );
472    for( i=0; i<inf->webseedCount; ++i )
473    {
474        GtkTreeIter iter;
475        gtk_tree_model_iter_nth_child( GTK_TREE_MODEL( p->webseeds ), &iter, NULL, i );
476        gtk_list_store_set( p->webseeds, &iter, WEBSEED_COL_DOWNLOAD_RATE, speeds[i],
477                                                -1 );
478    }
479    tr_free( speeds );
480  }
481
482  /**
483  ***  merge the peer diffs into the tree model.
484  ***
485  ***  this is more complicated than creating a new model,
486  ***  but is also (a) more efficient and (b) doesn't undo
487  ***  the view's visible area and sorting on every refresh.
488  **/
489
490  n_peers = 0;
491  peers = tr_torrentPeers (tor, &n_peers);
492  qsort (peers, n_peers, sizeof(tr_peer_stat), compare_peers);
493
494  i = 0;
495  if (gtk_tree_model_get_iter_first (model, &iter)) do
496  {
497    char * addr = NULL;
498    tr_peer_stat * peer = NULL;
499    gtk_tree_model_get (model, &iter, PEER_COL_ADDRESS, &addr, -1);
500    peer = bsearch (addr, peers, n_peers, sizeof(tr_peer_stat),
501                    compare_addr_to_peer);
502    g_free (addr);
503
504    if (peer) /* update a pre-existing row */
505    {
506      const int pos = peer - peers;
507      const int n_rhs = n_peers - (pos+1);
508      g_assert (n_rhs >= 0);
509
510      peer_row_set (store, &iter, peer);
511
512      /* remove it from the tr_peer_stat list */
513      g_memmove (peer, peer+1, sizeof(tr_peer_stat)*n_rhs);
514      --n_peers;
515    }
516    else if (!gtk_list_store_remove (store, &iter))
517      break; /* we removed the model's last item */
518  }
519  while (gtk_tree_model_iter_next (model, &iter));
520
521  append_peers_to_model (store, peers, n_peers);  /* all these are new */
522
523#ifdef SHOW_PIECES
524  if (GDK_IS_DRAWABLE (p->completeness->window))
525    refresh_pieces (p->completeness, NULL, p->gtor);
526#endif
527
528  fmtpeercount (p->seeders_lb, stat->seeders);
529  fmtpeercount (p->leechers_lb, stat->leechers);
530  fmtpeercount (p->completed_lb, stat->completedFromTracker );
531
532  free( peers );
533}
534
535#if GTK_CHECK_VERSION(2,12,0)
536static gboolean
537onPeerViewQueryTooltip( GtkWidget  * widget,
538                        gint         x,
539                        gint         y,
540                        gboolean     keyboard_tip,
541                        GtkTooltip * tooltip,
542                        gpointer     user_data UNUSED )
543{
544    gboolean show_tip = FALSE;
545    GtkTreeModel * model;
546    GtkTreeIter iter;
547
548    if( gtk_tree_view_get_tooltip_context( GTK_TREE_VIEW( widget ),
549                                           &x, &y, keyboard_tip,
550                                           &model, NULL, &iter ) )
551    {
552        const char * pch;
553        char * str = NULL;
554        GString * gstr = g_string_new( NULL );
555        gtk_tree_model_get( model, &iter, PEER_COL_STATUS, &str, -1 );
556        for( pch=str; pch && *pch; ++pch ) {
557            const char * txt = NULL;
558            switch( *pch ) {
559                case 'O': txt = _( "Optimistic unchoke" ); break;
560                case 'D': txt = _( "Downloading from peer" ); break;
561                case 'd': txt = _( "We'd download from peer if they'd let us" ); break;
562                case 'U': txt = _( "Uploading to peer" ); break;
563                case 'u': txt = _( "We'd upload to peer if they'd ask us" ); break;
564                case 'K': txt = _( "Peer has unchoked us, but we're not interested" ); break;
565                case '?': txt = _( "We unchoked the peer, but they're not interested" ); break;
566                case 'E': txt = _( "Encrypted connection" ); break;
567                case 'X': txt = _( "Peer was discovered through Peer Exchange (PEX)" ); break;
568                case 'I': txt = _( "Peer is an incoming connection" ); break;
569            }
570            if( txt )
571                g_string_append_printf( gstr, "%c: %s\n", *pch, txt );
572        }
573        if( gstr->len ) /* remove the last linefeed */
574            g_string_set_size( gstr, gstr->len-1 );
575        gtk_tooltip_set_text( tooltip, gstr->str );
576        g_string_free( gstr, TRUE );
577        g_free( str );
578        show_tip = TRUE;
579    }
580
581    return show_tip;
582
583}
584#endif
585
586static GtkWidget*
587peer_page_new ( TrTorrent * gtor )
588{
589  guint i;
590  GtkTreeModel *m;
591  GtkWidget *v, *w, *ret, *sw, *l, *vbox, *hbox;
592  GtkWidget *webtree = NULL;
593  tr_torrent * tor = tr_torrent_handle (gtor);
594  PeerData * p = g_new (PeerData, 1);
595  const tr_info * inf = tr_torrent_info( gtor );
596
597  /* TODO: make this configurable? */
598  int view_columns[] = { PEER_COL_IS_ENCRYPTED,
599                         PEER_COL_UPLOAD_RATE,
600                         PEER_COL_DOWNLOAD_RATE,
601                         PEER_COL_PROGRESS,
602                         PEER_COL_STATUS,
603                         PEER_COL_ADDRESS,
604                         PEER_COL_CLIENT };
605
606 
607  if( inf->webseedCount )
608  {
609    GtkTreeViewColumn * c;
610    GtkCellRenderer * r;
611    const char * t;
612    GtkWidget * fr;
613
614    m = webseed_model_new( tr_torrent_handle( gtor ) );
615    webtree = gtk_tree_view_new_with_model( m );
616    gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( webtree ), TRUE );
617    p->webseeds = GTK_LIST_STORE( m );
618    g_object_unref( G_OBJECT( m ) );
619
620    t = _( webseed_column_names[WEBSEED_COL_URL] );
621    r = gtk_cell_renderer_text_new ();
622    g_object_set( G_OBJECT( r ), "ellipsize", PANGO_ELLIPSIZE_END, NULL );
623    c = gtk_tree_view_column_new_with_attributes( t, r, "text", WEBSEED_COL_URL, NULL);
624    g_object_set( G_OBJECT( c ), "expand", TRUE, NULL );
625    gtk_tree_view_column_set_sort_column_id( c, WEBSEED_COL_URL );
626    gtk_tree_view_append_column( GTK_TREE_VIEW( webtree ), c );
627
628    t = _( webseed_column_names[WEBSEED_COL_DOWNLOAD_RATE] );
629    r = gtk_cell_renderer_text_new ();
630    c = gtk_tree_view_column_new_with_attributes (t, r, "text", WEBSEED_COL_DOWNLOAD_RATE, NULL);
631    gtk_tree_view_column_set_cell_data_func (c, r, render_dl_rate,
632                                                   NULL, NULL);
633    gtk_tree_view_column_set_sort_column_id( c, WEBSEED_COL_DOWNLOAD_RATE );
634    gtk_tree_view_append_column( GTK_TREE_VIEW( webtree ), c );
635
636    fr = gtk_frame_new( NULL );
637    gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_IN );
638    gtk_container_add( GTK_CONTAINER( fr ), webtree );
639    webtree = fr;
640  } 
641
642  m  = peer_model_new (tor);
643  v = GTK_WIDGET( g_object_new( GTK_TYPE_TREE_VIEW,
644                                "model", m,
645                                "rules-hint", TRUE,
646#if GTK_CHECK_VERSION(2,12,0)
647                                "has-tooltip", TRUE,
648#endif
649                                NULL ) );
650#if GTK_CHECK_VERSION(2,12,0)
651  g_signal_connect( v, "query-tooltip",
652                    G_CALLBACK(onPeerViewQueryTooltip), NULL );
653#endif
654  gtk_widget_set_size_request( v, 550, 0 );
655  g_object_unref (G_OBJECT(m));
656
657  for (i=0; i<G_N_ELEMENTS(view_columns); ++i)
658  {
659    const int col = view_columns[i];
660    const char * t = _(peer_column_names[col]);
661    gboolean resizable = TRUE;
662    GtkTreeViewColumn * c;
663    GtkCellRenderer * r;
664
665    switch (col)
666    {
667      case PEER_COL_ADDRESS:
668        r = gtk_cell_renderer_text_new ();
669        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
670        break;
671
672      case PEER_COL_CLIENT:
673        r = gtk_cell_renderer_text_new ();
674        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
675        gtk_tree_view_column_set_cell_data_func (c, r, render_client,
676                                                 NULL, NULL);
677        break;
678
679      case PEER_COL_PROGRESS:
680        r = gtk_cell_renderer_progress_new ();
681        c = gtk_tree_view_column_new_with_attributes (t, r, "value", PEER_COL_PROGRESS, NULL);
682        break;
683
684      case PEER_COL_IS_ENCRYPTED:
685        resizable = FALSE;
686        r = gtk_cell_renderer_pixbuf_new ();
687        c = gtk_tree_view_column_new_with_attributes (t, r, NULL);
688        gtk_tree_view_column_set_sizing (c, GTK_TREE_VIEW_COLUMN_FIXED);
689        gtk_tree_view_column_set_fixed_width (c, 20);
690        gtk_tree_view_column_set_cell_data_func (c, r, render_encrypted,
691                                                 NULL, NULL);
692        break;
693
694      case PEER_COL_DOWNLOAD_RATE:
695        r = gtk_cell_renderer_text_new ();
696        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
697        gtk_tree_view_column_set_cell_data_func (c, r, render_dl_rate,
698                                                 NULL, NULL);
699        break;
700
701      case PEER_COL_UPLOAD_RATE:
702        r = gtk_cell_renderer_text_new ();
703        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
704        gtk_tree_view_column_set_cell_data_func (c, r, render_ul_rate,
705                                                 NULL, NULL);
706        break;
707
708      case PEER_COL_STATUS:
709        r = gtk_cell_renderer_text_new( );
710        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
711        break;
712
713      default:
714        abort ();
715    }
716
717    gtk_tree_view_column_set_resizable (c, resizable);
718    gtk_tree_view_column_set_sort_column_id (c, col);
719    gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
720  }
721
722  /* the 'expander' column has a 10-pixel margin on the left
723     that doesn't look quite correct in any of these columns...
724     so create a non-visible column and assign it as the
725     'expander column. */
726  {
727    GtkTreeViewColumn *c = gtk_tree_view_column_new ();
728    gtk_tree_view_column_set_visible (c, FALSE);
729    gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
730    gtk_tree_view_set_expander_column (GTK_TREE_VIEW(v), c);
731  }
732
733  w = sw = gtk_scrolled_window_new (NULL, NULL);
734  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(w),
735                                  GTK_POLICY_NEVER,
736                                  GTK_POLICY_AUTOMATIC);
737  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(w),
738                                       GTK_SHADOW_IN);
739  gtk_container_add (GTK_CONTAINER(w), v);
740
741
742  vbox = gtk_vbox_new (FALSE, GUI_PAD);
743  gtk_container_set_border_width (GTK_CONTAINER(vbox), GUI_PAD_BIG);
744
745    if( webtree )
746        gtk_box_pack_start( GTK_BOX( vbox ), webtree, FALSE, FALSE, 0 );
747
748    //h = gtk_hbox_new (FALSE, GUI_PAD);
749    //gtk_box_pack_start_defaults (GTK_BOX(h), sw);
750    gtk_box_pack_start_defaults( GTK_BOX( vbox ), sw );
751
752    hbox = gtk_hbox_new (FALSE, GUI_PAD);
753        l = gtk_label_new (NULL);
754        gtk_label_set_markup (GTK_LABEL(l), _( "<b>Seeders:</b>" ) );
755        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
756        l = p->seeders_lb = gtk_label_new (NULL);
757        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
758    gtk_box_pack_start_defaults (GTK_BOX(hbox),
759                                 gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
760        l = gtk_label_new (NULL);
761        gtk_label_set_markup (GTK_LABEL(l), _( "<b>Leechers:</b>" ) );
762        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
763        l = p->leechers_lb = gtk_label_new (NULL);
764        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
765    gtk_box_pack_start_defaults (GTK_BOX(hbox),
766                                 gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
767        l = gtk_label_new (NULL);
768        gtk_label_set_markup (GTK_LABEL(l), _( "<b>Completed:</b>" ) );
769        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
770        l = p->completed_lb = gtk_label_new (NULL);
771        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
772    gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
773
774  ret = vbox;
775  p->gtor = gtor;
776  p->model = m;
777  p->store = GTK_LIST_STORE(m);
778  g_object_set_data_full (G_OBJECT(ret), "peer-data", p, g_free);
779  return ret;
780}
781
782/****
783*****  INFO TAB
784****/
785
786static void
787refresh_time_lb( GtkWidget * l, time_t t )
788{
789    const char * never = _( "Never" );
790    if( !t )
791        gtk_label_set_text( GTK_LABEL( l ), never );
792    else {
793        char * str = rfc822date( t );
794        gtk_label_set_text( GTK_LABEL( l ), str );
795        g_free( str );
796    }
797}
798
799static GtkWidget*
800info_page_new (tr_torrent * tor)
801{
802  int row = 0;
803  GtkWidget *t = hig_workarea_create ();
804  GtkWidget *l, *w, *fr;
805  char *pch;
806  char sizeStr[128];
807  char countStr[128];
808  char buf[256];
809  GtkTextBuffer * b;
810  const tr_info * info = tr_torrentInfo(tor);
811
812  hig_workarea_add_section_title (t, &row, _("Details"));
813
814    g_snprintf( countStr, sizeof( countStr ),
815                ngettext( "%d Piece", "%d Pieces", info->pieceCount ),
816                info->pieceCount );
817    tr_strlsize( sizeStr, info->pieceSize, sizeof(sizeStr) );
818    g_snprintf( buf, sizeof( buf ),
819                /* %1$s is number of pieces;
820                   %2$s is how big each piece is */
821                _( "%1$s @ %2$s" ),
822              countStr, sizeStr );
823
824    l = gtk_label_new (buf);
825    hig_workarea_add_row (t, &row, _("Pieces:"), l, NULL);
826
827    l = g_object_new( GTK_TYPE_LABEL, "label", info->hashString, "selectable", TRUE,
828                                      "ellipsize", PANGO_ELLIPSIZE_END, NULL );
829    hig_workarea_add_row (t, &row, _("Hash:"), l, NULL);
830
831    pch = (info->isPrivate )
832      ? _("Private to this tracker -- PEX disabled")
833      : _("Public torrent");
834    l = gtk_label_new (pch);
835    hig_workarea_add_row (t, &row, _("Privacy:"), l, NULL);
836
837    b = gtk_text_buffer_new (NULL);
838    if( info->comment )
839        gtk_text_buffer_set_text (b, info->comment, -1);
840    w = gtk_text_view_new_with_buffer (b);
841    gtk_widget_set_size_request (w, 0u, 100u);
842    gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW(w), GTK_WRAP_WORD); 
843    gtk_text_view_set_editable (GTK_TEXT_VIEW(w), FALSE);
844    fr = gtk_frame_new (NULL);
845    gtk_frame_set_shadow_type (GTK_FRAME(fr), GTK_SHADOW_IN);
846    gtk_container_add (GTK_CONTAINER(fr), w);
847    w = hig_workarea_add_row (t, &row, _("Comment:"), fr, NULL);
848    gtk_misc_set_alignment (GTK_MISC(w), 0.0f, 0.0f);
849
850  hig_workarea_add_section_divider (t, &row);
851  hig_workarea_add_section_title (t, &row, _("Origins"));
852 
853    l = gtk_label_new (*info->creator ? info->creator : _("Unknown"));
854    hig_workarea_add_row (t, &row, _("Creator:"), l, NULL);
855
856    l = gtk_label_new( NULL );
857    refresh_time_lb( l, info->dateCreated );
858    hig_workarea_add_row (t, &row, _("Date:"), l, NULL); 
859
860  hig_workarea_add_section_divider (t, &row);
861  hig_workarea_add_section_title (t, &row, _("Location"));
862
863    l = g_object_new( GTK_TYPE_LABEL, "label", tr_torrentGetDownloadDir( tor ), "selectable", TRUE,
864                                      "ellipsize", PANGO_ELLIPSIZE_END, NULL );
865    hig_workarea_add_row (t, &row, _( "Destination folder:" ), l, NULL); 
866
867    l = g_object_new( GTK_TYPE_LABEL, "label", info->torrent, "selectable", TRUE,
868                                      "ellipsize", PANGO_ELLIPSIZE_END, NULL );
869    hig_workarea_add_row (t, &row, _( "Torrent file:" ), l, NULL); 
870
871  hig_workarea_finish (t, &row);
872  return t;
873}
874
875/****
876*****  ACTIVITY TAB
877****/
878
879typedef struct
880{
881  GtkWidget * state_lb;
882  GtkWidget * progress_lb;
883  GtkWidget * have_lb;
884  GtkWidget * dl_lb;
885  GtkWidget * ul_lb;
886  GtkWidget * failed_lb;
887  GtkWidget * ratio_lb;
888  GtkWidget * err_lb;
889  GtkWidget * swarm_lb;
890  GtkWidget * date_added_lb;
891  GtkWidget * last_activity_lb;
892  GtkWidget * availability_da;
893  TrTorrent * gtor;
894}
895Activity;
896
897static void
898refresh_activity (GtkWidget * top)
899{
900  Activity * a = g_object_get_data (G_OBJECT(top), "activity-data");
901  char *pch;
902  char sizeStr[64];
903  char sizeStr2[64];
904  char buf[128];
905  const tr_stat * stat = tr_torrent_stat( a->gtor );
906  const double complete = stat->percentComplete * 100.0;
907  const double done = stat->percentDone * 100.0;
908
909  pch = tr_torrent_status_str( a->gtor );
910  gtk_label_set_text (GTK_LABEL(a->state_lb), pch);
911  g_free (pch);
912
913  if( (int)complete == (int)done )
914    pch = g_strdup_printf( _( "%.1f%%" ), complete );
915  else
916    /* %1$.1f is percent of how much of what we want's been downloaded,
917       %2$.1f is percent of how much of the whole torrent we've downloaded */
918    pch = g_strdup_printf( _( "%1$.1f%% (%2$.1f%% selected)" ), complete, done );
919  gtk_label_set_text (GTK_LABEL(a->progress_lb), pch);
920  g_free (pch);
921
922  tr_strlsize( sizeStr,  stat->haveValid + stat->haveUnchecked, sizeof(sizeStr) );
923  tr_strlsize( sizeStr2, stat->haveValid,                       sizeof(sizeStr2) );
924  /* %1$s is total size of what we've saved to disk
925     %2$s is how much of it's passed the checksum test */
926  g_snprintf( buf, sizeof(buf), _("%1$s (%2$s verified)"), sizeStr, sizeStr2 );
927  gtk_label_set_text( GTK_LABEL( a->have_lb ), buf );
928
929  tr_strlsize( sizeStr, stat->downloadedEver, sizeof(sizeStr) );
930  gtk_label_set_text( GTK_LABEL(a->dl_lb), sizeStr );
931
932  tr_strlsize( sizeStr, stat->uploadedEver, sizeof(sizeStr) );
933  gtk_label_set_text( GTK_LABEL(a->ul_lb), sizeStr );
934
935  tr_strlsize( sizeStr, stat->corruptEver, sizeof( sizeStr ) );
936  gtk_label_set_text( GTK_LABEL( a->failed_lb ), sizeStr );
937
938  tr_strlratio( buf, stat->ratio, sizeof( buf ) );
939  gtk_label_set_text( GTK_LABEL( a->ratio_lb ), buf );
940
941  tr_strlspeed( buf, stat->swarmSpeed, sizeof(buf) );
942  gtk_label_set_text (GTK_LABEL(a->swarm_lb), buf );
943
944  gtk_label_set_text (GTK_LABEL(a->err_lb),
945                      *stat->errorString ? stat->errorString : _("None"));
946
947  refresh_time_lb( a->date_added_lb, stat->addedDate );
948
949  refresh_time_lb( a->last_activity_lb, stat->activityDate );
950
951#ifdef SHOW_PIECES
952  if (GDK_IS_DRAWABLE (a->availability_da->window))
953    refresh_pieces (a->availability_da, NULL, a->gtor);
954#endif
955}
956 
957
958static GtkWidget*
959activity_page_new (TrTorrent * gtor)
960{
961  Activity * a = g_new (Activity, 1);
962  int row = 0;
963  GtkWidget *t = hig_workarea_create ();
964  GtkWidget *l;
965
966  a->gtor = gtor;
967
968  hig_workarea_add_section_title (t, &row, _("Transfer"));
969
970    l = a->state_lb = gtk_label_new (NULL);
971    hig_workarea_add_row (t, &row, _("State:"), l, NULL);
972
973    l = a->progress_lb = gtk_label_new (NULL);
974    hig_workarea_add_row (t, &row, _("Progress:"), l, NULL);
975
976    l = a->have_lb = gtk_label_new (NULL);
977    /* "Have" refers to how much of the torrent we have */
978    hig_workarea_add_row (t, &row, _("Have:"), l, NULL);
979
980    l = a->dl_lb = gtk_label_new (NULL);
981    hig_workarea_add_row (t, &row, _("Downloaded:"), l, NULL);
982
983    l = a->ul_lb = gtk_label_new (NULL);
984    hig_workarea_add_row (t, &row, _("Uploaded:"), l, NULL);
985
986    /* how much downloaded data was corrupt */
987    l = a->failed_lb = gtk_label_new (NULL);
988    hig_workarea_add_row (t, &row, _("Failed DL:"), l, NULL);
989
990    l = a->ratio_lb = gtk_label_new (NULL);
991    hig_workarea_add_row (t, &row, _("Ratio:"), l, NULL);
992
993    l = a->swarm_lb = gtk_label_new (NULL);
994    hig_workarea_add_row (t, &row, _("Swarm rate:"), l, NULL);
995
996    l = a->err_lb = gtk_label_new (NULL);
997    hig_workarea_add_row (t, &row, _("Error:"), l, NULL);
998
999#ifdef SHOW_PIECES
1000  hig_workarea_add_section_divider (t, &row);
1001  hig_workarea_add_section_title (t, &row, _("Completion"));
1002
1003    w = a->availability_da = gtk_drawing_area_new ();
1004    gtk_widget_set_size_request (w, 0u, 100u);
1005    g_object_set_data (G_OBJECT(w), "draw-mode", GINT_TO_POINTER(DRAW_PROG));
1006    g_signal_connect (w, "expose-event", G_CALLBACK(refresh_pieces), gtor);
1007    hig_workarea_add_wide_control( t, &row, w );
1008#endif
1009
1010  hig_workarea_add_section_divider (t, &row);
1011  hig_workarea_add_section_title (t, &row, _("Dates"));
1012
1013    l = a->date_added_lb = gtk_label_new (NULL);
1014    hig_workarea_add_row (t, &row, _("Started at:"), l, NULL);
1015
1016    l = a->last_activity_lb = gtk_label_new (NULL);
1017    hig_workarea_add_row (t, &row, _("Last activity at:"), l, NULL);
1018
1019  hig_workarea_add_section_divider (t, &row);
1020  hig_workarea_finish (t, &row);
1021  g_object_set_data_full (G_OBJECT(t), "activity-data", a, g_free);
1022  return t;
1023}
1024
1025
1026/****
1027*****  OPTIONS
1028****/
1029
1030static void
1031speed_toggled_cb( GtkToggleButton * tb, gpointer gtor, int up_or_down )
1032{
1033  tr_torrent * tor = tr_torrent_handle (gtor);
1034  gboolean b = gtk_toggle_button_get_active(tb);
1035  tr_torrentSetSpeedMode( tor, up_or_down, b ? TR_SPEEDLIMIT_SINGLE
1036                                             : TR_SPEEDLIMIT_GLOBAL );
1037}
1038static void
1039ul_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
1040{
1041  speed_toggled_cb( tb, gtor, TR_UP );
1042}
1043static void
1044dl_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
1045{
1046  speed_toggled_cb( tb, gtor, TR_DOWN );
1047}
1048
1049
1050static void
1051sensitize_from_check_cb (GtkToggleButton *toggle, gpointer w)
1052{
1053  gtk_widget_set_sensitive (GTK_WIDGET(w),
1054                            gtk_toggle_button_get_active(toggle));
1055}
1056
1057static void
1058setSpeedLimit( GtkSpinButton* spin, gpointer gtor, int up_or_down )
1059{
1060  tr_torrent * tor = tr_torrent_handle (gtor);
1061  int kb_sec = gtk_spin_button_get_value_as_int (spin);
1062  tr_torrentSetSpeedLimit( tor, up_or_down, kb_sec );
1063}
1064static void
1065ul_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
1066{
1067  setSpeedLimit( spin, gtor, TR_UP );
1068}
1069static void
1070dl_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
1071{
1072  setSpeedLimit( spin, gtor, TR_DOWN );
1073}
1074
1075static void
1076max_peers_spun_cb( GtkSpinButton * spin, gpointer gtor )
1077{
1078  const uint16_t n = gtk_spin_button_get_value( spin );
1079  tr_torrentSetPeerLimit( tr_torrent_handle( gtor ), n );
1080}
1081
1082static GtkWidget*
1083options_page_new ( TrTorrent * gtor )
1084{
1085  uint16_t maxConnectedPeers;
1086  int i, row;
1087  gboolean b;
1088  GtkAdjustment *a;
1089  GtkWidget *t, *w, *tb;
1090  tr_torrent * tor = tr_torrent_handle (gtor);
1091
1092  row = 0;
1093  t = hig_workarea_create ();
1094  hig_workarea_add_section_title (t, &row, _("Bandwidth") );
1095
1096    tb = gtk_check_button_new_with_mnemonic (_("Limit _download speed (KB/s):"));
1097    b = tr_torrentGetSpeedMode(tor,TR_DOWN) == TR_SPEEDLIMIT_SINGLE;
1098    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), b );
1099    g_signal_connect (tb, "toggled", G_CALLBACK(dl_speed_toggled_cb), gtor);
1100
1101    i = tr_torrentGetSpeedLimit( tor, TR_DOWN );
1102    a = (GtkAdjustment*) gtk_adjustment_new (i, 0.0, G_MAXDOUBLE, 1, 1, 1);
1103    w = gtk_spin_button_new (a, 1, 0);
1104    g_signal_connect (w, "value-changed", G_CALLBACK(dl_speed_spun_cb), gtor);
1105    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1106    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1107    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1108
1109    tb = gtk_check_button_new_with_mnemonic (_("Limit _upload speed (KB/s):"));
1110    b = tr_torrentGetSpeedMode(tor,TR_UP) == TR_SPEEDLIMIT_SINGLE;
1111    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), b );
1112    g_signal_connect (tb, "toggled", G_CALLBACK(ul_speed_toggled_cb), gtor);
1113
1114    i = tr_torrentGetSpeedLimit( tor, TR_UP );
1115    a = (GtkAdjustment*) gtk_adjustment_new (i, 0.0, G_MAXDOUBLE, 1, 1, 1);
1116    w = gtk_spin_button_new (a, 1, 0);
1117    g_signal_connect (w, "value-changed", G_CALLBACK(ul_speed_spun_cb), gtor);
1118    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1119    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1120    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1121
1122  hig_workarea_add_section_divider (t, &row);
1123  hig_workarea_add_section_title (t, &row, _("Peer Connections"));
1124
1125    maxConnectedPeers = tr_torrentGetPeerLimit( tor );
1126    w = gtk_spin_button_new_with_range( 1, 3000, 5 );
1127    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), maxConnectedPeers );
1128    hig_workarea_add_row( t, &row, _( "_Maximum peers:" ), w, w );
1129    g_signal_connect( w, "value-changed", G_CALLBACK( max_peers_spun_cb ), gtor );
1130
1131  hig_workarea_finish (t, &row);
1132  return t;
1133}
1134
1135static void
1136refresh_options (GtkWidget * top UNUSED)
1137{
1138}
1139
1140/****
1141*****  TRACKER
1142****/
1143
1144#define TRACKER_PAGE "tracker-page"
1145
1146struct tracker_page
1147{
1148    TrTorrent * gtor;
1149
1150    GtkTreeView * view;
1151    GtkListStore * store;
1152    GtkTreeSelection * sel;
1153
1154    GtkWidget * add_button;
1155    GtkWidget * remove_button;
1156    GtkWidget * save_button;
1157    GtkWidget * revert_button;
1158
1159    GtkWidget * last_scrape_time_lb;
1160    GtkWidget * last_scrape_response_lb;
1161    GtkWidget * next_scrape_countdown_lb;
1162
1163    GtkWidget * last_announce_time_lb;
1164    GtkWidget * last_announce_response_lb;
1165    GtkWidget * next_announce_countdown_lb;
1166    GtkWidget * manual_announce_countdown_lb;
1167};
1168
1169GtkWidget*
1170tracker_page_new( TrTorrent * gtor )
1171{
1172    GtkWidget * t;
1173    GtkWidget * l;
1174    GtkWidget * w;
1175    int row = 0;
1176    const char * s;
1177    struct tracker_page * page = g_new0( struct tracker_page, 1 );
1178    const tr_info * info = tr_torrent_info (gtor);
1179
1180    page->gtor = gtor;
1181
1182    t = hig_workarea_create( );
1183    hig_workarea_add_section_title( t, &row, _( "Trackers" ) );
1184
1185        w = tracker_list_new( gtor );
1186        hig_workarea_add_wide_control( t, &row, w );
1187
1188    hig_workarea_add_section_divider( t, &row );
1189    hig_workarea_add_section_title( t, &row, _( "Scrape" ) );
1190
1191        s = _( "Last scrape at:" );
1192        l = gtk_label_new( NULL );
1193        page->last_scrape_time_lb = l;
1194        hig_workarea_add_row( t, &row, s, l, NULL );
1195
1196        s = _( "Tracker responded:");
1197        l = gtk_label_new( NULL );
1198        page->last_scrape_response_lb = l;
1199        hig_workarea_add_row( t, &row, s, l, NULL );
1200
1201        s = _( "Next scrape in:" );
1202        l = gtk_label_new( NULL );
1203        page->next_scrape_countdown_lb = l;
1204        hig_workarea_add_row( t, &row, s, l, NULL );
1205
1206    hig_workarea_add_section_divider( t, &row );
1207    hig_workarea_add_section_title( t, &row, _( "Announce" ) );
1208
1209        l = gtk_label_new( info->trackers[0].announce );
1210        gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
1211        hig_workarea_add_row (t, &row, _( "Tracker:" ), l, NULL);
1212
1213        s = _( "Last announce at:" );
1214        l = gtk_label_new( NULL );
1215        page->last_announce_time_lb = l;
1216        hig_workarea_add_row( t, &row, s, l, NULL );
1217
1218        s = _( "Tracker responded:");
1219        l = gtk_label_new( NULL );
1220        page->last_announce_response_lb = l;
1221        hig_workarea_add_row( t, &row, s, l, NULL );
1222
1223        s = _( "Next announce in:" );
1224        l = gtk_label_new( NULL );
1225        page->next_announce_countdown_lb = l;
1226        hig_workarea_add_row( t, &row, s, l, NULL );
1227
1228        /* how long until the tracker will honor user
1229         * pressing the "ask for more peers" button */
1230        s = _( "Manual announce allowed in:" );
1231        l = gtk_label_new( NULL );
1232        page->manual_announce_countdown_lb = l;
1233        hig_workarea_add_row( t, &row, s, l, NULL );
1234
1235    hig_workarea_finish( t, &row );
1236    g_object_set_data_full( G_OBJECT( t ), TRACKER_PAGE, page, g_free );
1237    return t;
1238}
1239
1240static void
1241refresh_countdown_lb( GtkWidget * l, time_t t )
1242{
1243    const time_t now = time( NULL );
1244
1245    if( !t || ( t < now ) )
1246        gtk_label_set_text( GTK_LABEL( l ), _( "Never" ) );
1247    else {
1248        char buf[1024];
1249        const int seconds = t - now;
1250        tr_strltime( buf, seconds, sizeof( buf ) );
1251        gtk_label_set_text( GTK_LABEL( l ), buf );
1252    }
1253}
1254
1255static void
1256refresh_tracker( GtkWidget * w )
1257{
1258    GtkWidget * l;
1259    time_t t;
1260    struct tracker_page * page = g_object_get_data( G_OBJECT( w ), TRACKER_PAGE );
1261    const tr_stat * torStat = tr_torrent_stat( page->gtor );
1262
1263    l = page->last_scrape_time_lb;
1264    t = torStat->lastScrapeTime;
1265    refresh_time_lb( l, t );
1266
1267    l = page->last_scrape_response_lb;
1268    gtk_label_set_text( GTK_LABEL( l ), torStat->scrapeResponse );
1269
1270    l = page->next_scrape_countdown_lb;
1271    t = torStat->nextScrapeTime;
1272    refresh_countdown_lb( l, t );
1273
1274    l = page->last_announce_time_lb;
1275    t = torStat->lastAnnounceTime;
1276    refresh_time_lb( l, t );
1277
1278    l = page->last_announce_response_lb;
1279    gtk_label_set_text( GTK_LABEL( l ), torStat->announceResponse );
1280
1281    l = page->next_announce_countdown_lb;
1282    t = torStat->nextAnnounceTime;
1283    refresh_countdown_lb( l, t );
1284
1285    l = page->manual_announce_countdown_lb;
1286    t = torStat->manualAnnounceTime;
1287    refresh_countdown_lb( l, t );
1288}
1289
1290/****
1291*****  DIALOG
1292****/
1293
1294static void
1295torrent_destroyed (gpointer dialog, GObject * dead_torrent UNUSED)
1296{
1297  gtk_widget_destroy (GTK_WIDGET(dialog));
1298}
1299
1300static void
1301remove_tag (gpointer tag)
1302{
1303  g_source_remove (GPOINTER_TO_UINT(tag)); /* stop the periodic refresh */
1304}
1305
1306static void
1307response_cb (GtkDialog *dialog, int response UNUSED, gpointer gtor)
1308{
1309  g_object_weak_unref (G_OBJECT(gtor), torrent_destroyed, dialog);
1310  gtk_widget_destroy (GTK_WIDGET(dialog));
1311}
1312
1313static gboolean
1314periodic_refresh (gpointer data)
1315{
1316  refresh_tracker   (g_object_get_data (G_OBJECT(data), "tracker-top"));
1317  refresh_peers     (g_object_get_data (G_OBJECT(data), "peers-top"));
1318  refresh_activity  (g_object_get_data (G_OBJECT(data), "activity-top"));
1319  refresh_options   (g_object_get_data (G_OBJECT(data), "options-top"));
1320  return TRUE;
1321}
1322
1323GtkWidget*
1324torrent_inspector_new ( GtkWindow * parent, TrTorrent * gtor )
1325{
1326  guint tag;
1327  GtkWidget *d, *n, *w;
1328  tr_torrent * tor = tr_torrent_handle (gtor);
1329  char sizeStr[64];
1330  char title[512];
1331  const tr_info * info = tr_torrent_info (gtor);
1332
1333  /* create the dialog */
1334  tr_strlsize( sizeStr, info->totalSize, sizeof(sizeStr) );
1335  /* %1$s is torrent name
1336     %2$s its file size */
1337  g_snprintf( title, sizeof(title), _( "Details for %1$s (%2$s)" ), info->name, sizeStr );
1338  d = gtk_dialog_new_with_buttons (title, parent, 0,
1339                                   GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1340                                   NULL);
1341  gtk_window_set_role (GTK_WINDOW(d), "tr-info" );
1342  g_signal_connect (d, "response", G_CALLBACK (response_cb), gtor);
1343  gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
1344  gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1345  g_object_weak_ref (G_OBJECT(gtor), torrent_destroyed, d);
1346 
1347
1348  /* add the notebook */
1349  n = gtk_notebook_new ();
1350  gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1351
1352  w = activity_page_new (gtor);
1353  g_object_set_data (G_OBJECT(d), "activity-top", w);
1354  gtk_notebook_append_page (GTK_NOTEBOOK(n), w, 
1355                            gtk_label_new (_("Activity")));
1356
1357  w = peer_page_new (gtor);
1358  g_object_set_data (G_OBJECT(d), "peers-top", w);
1359  gtk_notebook_append_page (GTK_NOTEBOOK(n),  w,
1360                            gtk_label_new (_("Peers")));
1361
1362  w = tracker_page_new( gtor );
1363  g_object_set_data( G_OBJECT( d ), "tracker-top", w );
1364  gtk_notebook_append_page( GTK_NOTEBOOK( n ), w,
1365                            gtk_label_new( _( "Tracker" ) ) );
1366
1367  gtk_notebook_append_page (GTK_NOTEBOOK(n),
1368                            info_page_new (tor),
1369                            gtk_label_new (_("Information")));
1370
1371  w = file_list_new( gtor );
1372  gtk_container_set_border_width( GTK_CONTAINER( w ), GUI_PAD_BIG );
1373  g_object_set_data (G_OBJECT(d), "files-top", w);
1374  gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
1375                            gtk_label_new (_("Files")));
1376
1377  w = options_page_new (gtor);
1378  g_object_set_data (G_OBJECT(d), "options-top", w);
1379  gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
1380                            gtk_label_new (_("Options")));
1381
1382  gtk_box_pack_start_defaults (GTK_BOX(GTK_DIALOG(d)->vbox), n);
1383
1384  tag = g_timeout_add (UPDATE_INTERVAL_MSEC, periodic_refresh, d);
1385  g_object_set_data_full (G_OBJECT(d), "tag",
1386                          GUINT_TO_POINTER(tag), remove_tag);
1387
1388  /* return the results */
1389  periodic_refresh (d);
1390  gtk_widget_show_all (GTK_DIALOG(d)->vbox);
1391  return d;
1392}
Note: See TracBrowser for help on using the repository browser.