source: trunk/gtk/details.c @ 6402

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

(gtk) #1108: transmission includes several unlocalized strings

  • Property svn:keywords set to Date Rev Author Id
File size: 43.9 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 6402 2008-07-25 14:56:28Z 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  /* 'download speed' column header. terse to keep the column narrow. */
287  N_("Down"),
288  /* 'upload speed' column header.  terse to keep the column narrow. */
289  N_("Up"),
290  N_("Client"),
291  /* 'percent done' column header. terse to keep the column narrow. */
292  N_("%"),
293  " ",
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  if (gtk_tree_model_get_iter_first (model, &iter)) do
495  {
496    char * addr = NULL;
497    tr_peer_stat * peer = NULL;
498    gtk_tree_model_get (model, &iter, PEER_COL_ADDRESS, &addr, -1);
499    peer = bsearch (addr, peers, n_peers, sizeof(tr_peer_stat),
500                    compare_addr_to_peer);
501    g_free (addr);
502
503    if (peer) /* update a pre-existing row */
504    {
505      const int pos = peer - peers;
506      const int n_rhs = n_peers - (pos+1);
507      g_assert (n_rhs >= 0);
508
509      peer_row_set (store, &iter, peer);
510
511      /* remove it from the tr_peer_stat list */
512      g_memmove (peer, peer+1, sizeof(tr_peer_stat)*n_rhs);
513      --n_peers;
514    }
515    else if (!gtk_list_store_remove (store, &iter))
516      break; /* we removed the model's last item */
517  }
518  while (gtk_tree_model_iter_next (model, &iter));
519
520  append_peers_to_model (store, peers, n_peers);  /* all these are new */
521
522#ifdef SHOW_PIECES
523  if (GDK_IS_DRAWABLE (p->completeness->window))
524    refresh_pieces (p->completeness, NULL, p->gtor);
525#endif
526
527  fmtpeercount (p->seeders_lb, stat->seeders );
528  fmtpeercount (p->leechers_lb, stat->leechers );
529  fmtpeercount (p->completed_lb, stat->timesCompleted );
530
531  free( peers );
532}
533
534#if GTK_CHECK_VERSION(2,12,0)
535static gboolean
536onPeerViewQueryTooltip( GtkWidget  * widget,
537                        gint         x,
538                        gint         y,
539                        gboolean     keyboard_tip,
540                        GtkTooltip * tooltip,
541                        gpointer     user_data UNUSED )
542{
543    gboolean show_tip = FALSE;
544    GtkTreeModel * model;
545    GtkTreeIter iter;
546
547    if( gtk_tree_view_get_tooltip_context( GTK_TREE_VIEW( widget ),
548                                           &x, &y, keyboard_tip,
549                                           &model, NULL, &iter ) )
550    {
551        const char * pch;
552        char * str = NULL;
553        GString * gstr = g_string_new( NULL );
554        gtk_tree_model_get( model, &iter, PEER_COL_STATUS, &str, -1 );
555        for( pch=str; pch && *pch; ++pch ) {
556            const char * txt = NULL;
557            switch( *pch ) {
558                case 'O': txt = _( "Optimistic unchoke" ); break;
559                case 'D': txt = _( "Downloading from this peer" ); break;
560                case 'd': txt = _( "We would download from this peer if they would let us" ); break;
561                case 'U': txt = _( "Uploading to peer" ); break;
562                case 'u': txt = _( "We would upload to this peer if they asked" ); break;
563                case 'K': txt = _( "Peer has unchoked us, but we're not interested" ); break;
564                case '?': txt = _( "We unchoked this peer, but they're not interested" ); break;
565                case 'E': txt = _( "Encrypted connection" ); break;
566                case 'X': txt = _( "Peer was discovered through Peer Exchange (PEX)" ); break;
567                case 'I': txt = _( "Peer is an incoming connection" ); break;
568            }
569            if( txt )
570                g_string_append_printf( gstr, "%c: %s\n", *pch, txt );
571        }
572        if( gstr->len ) /* remove the last linefeed */
573            g_string_set_size( gstr, gstr->len-1 );
574        gtk_tooltip_set_text( tooltip, gstr->str );
575        g_string_free( gstr, TRUE );
576        g_free( str );
577        show_tip = TRUE;
578    }
579
580    return show_tip;
581
582}
583#endif
584
585static GtkWidget*
586peer_page_new ( TrTorrent * gtor )
587{
588  guint i;
589  GtkTreeModel *m;
590  GtkWidget *v, *w, *ret, *sw, *l, *vbox, *hbox;
591  GtkWidget *webtree = NULL;
592  tr_torrent * tor = tr_torrent_handle (gtor);
593  PeerData * p = g_new (PeerData, 1);
594  const tr_info * inf = tr_torrent_info( gtor );
595
596  /* TODO: make this configurable? */
597  int view_columns[] = { PEER_COL_IS_ENCRYPTED,
598                         PEER_COL_UPLOAD_RATE,
599                         PEER_COL_DOWNLOAD_RATE,
600                         PEER_COL_PROGRESS,
601                         PEER_COL_STATUS,
602                         PEER_COL_ADDRESS,
603                         PEER_COL_CLIENT };
604
605 
606  if( inf->webseedCount )
607  {
608    GtkTreeViewColumn * c;
609    GtkCellRenderer * r;
610    const char * t;
611    GtkWidget * fr;
612
613    m = webseed_model_new( tr_torrent_handle( gtor ) );
614    webtree = gtk_tree_view_new_with_model( m );
615    gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( webtree ), TRUE );
616    p->webseeds = GTK_LIST_STORE( m );
617    g_object_unref( G_OBJECT( m ) );
618
619    t = _( webseed_column_names[WEBSEED_COL_URL] );
620    r = gtk_cell_renderer_text_new ();
621    g_object_set( G_OBJECT( r ), "ellipsize", PANGO_ELLIPSIZE_END, NULL );
622    c = gtk_tree_view_column_new_with_attributes( t, r, "text", WEBSEED_COL_URL, NULL);
623    g_object_set( G_OBJECT( c ), "expand", TRUE, NULL );
624    gtk_tree_view_column_set_sort_column_id( c, WEBSEED_COL_URL );
625    gtk_tree_view_append_column( GTK_TREE_VIEW( webtree ), c );
626
627    t = _( webseed_column_names[WEBSEED_COL_DOWNLOAD_RATE] );
628    r = gtk_cell_renderer_text_new ();
629    c = gtk_tree_view_column_new_with_attributes (t, r, "text", WEBSEED_COL_DOWNLOAD_RATE, NULL);
630    gtk_tree_view_column_set_cell_data_func (c, r, render_dl_rate,
631                                                   NULL, NULL);
632    gtk_tree_view_column_set_sort_column_id( c, WEBSEED_COL_DOWNLOAD_RATE );
633    gtk_tree_view_append_column( GTK_TREE_VIEW( webtree ), c );
634
635    fr = gtk_frame_new( NULL );
636    gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_IN );
637    gtk_container_add( GTK_CONTAINER( fr ), webtree );
638    webtree = fr;
639  } 
640
641  m  = peer_model_new (tor);
642  v = GTK_WIDGET( g_object_new( GTK_TYPE_TREE_VIEW,
643                                "model", m,
644                                "rules-hint", TRUE,
645#if GTK_CHECK_VERSION(2,12,0)
646                                "has-tooltip", TRUE,
647#endif
648                                NULL ) );
649#if GTK_CHECK_VERSION(2,12,0)
650  g_signal_connect( v, "query-tooltip",
651                    G_CALLBACK(onPeerViewQueryTooltip), NULL );
652#endif
653  gtk_widget_set_size_request( v, 550, 0 );
654  g_object_unref (G_OBJECT(m));
655
656  for (i=0; i<G_N_ELEMENTS(view_columns); ++i)
657  {
658    const int col = view_columns[i];
659    const char * t = _(peer_column_names[col]);
660    gboolean resizable = TRUE;
661    GtkTreeViewColumn * c;
662    GtkCellRenderer * r;
663
664    switch (col)
665    {
666      case PEER_COL_ADDRESS:
667        r = gtk_cell_renderer_text_new ();
668        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
669        break;
670
671      case PEER_COL_CLIENT:
672        r = gtk_cell_renderer_text_new ();
673        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
674        gtk_tree_view_column_set_cell_data_func (c, r, render_client,
675                                                 NULL, NULL);
676        break;
677
678      case PEER_COL_PROGRESS:
679        r = gtk_cell_renderer_progress_new ();
680        c = gtk_tree_view_column_new_with_attributes (t, r, "value", PEER_COL_PROGRESS, NULL);
681        break;
682
683      case PEER_COL_IS_ENCRYPTED:
684        resizable = FALSE;
685        r = gtk_cell_renderer_pixbuf_new ();
686        c = gtk_tree_view_column_new_with_attributes (t, r, NULL);
687        gtk_tree_view_column_set_sizing (c, GTK_TREE_VIEW_COLUMN_FIXED);
688        gtk_tree_view_column_set_fixed_width (c, 20);
689        gtk_tree_view_column_set_cell_data_func (c, r, render_encrypted,
690                                                 NULL, NULL);
691        break;
692
693      case PEER_COL_DOWNLOAD_RATE:
694        r = gtk_cell_renderer_text_new ();
695        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
696        gtk_tree_view_column_set_cell_data_func (c, r, render_dl_rate,
697                                                 NULL, NULL);
698        break;
699
700      case PEER_COL_UPLOAD_RATE:
701        r = gtk_cell_renderer_text_new ();
702        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
703        gtk_tree_view_column_set_cell_data_func (c, r, render_ul_rate,
704                                                 NULL, NULL);
705        break;
706
707      case PEER_COL_STATUS:
708        r = gtk_cell_renderer_text_new( );
709        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
710        break;
711
712      default:
713        abort ();
714    }
715
716    gtk_tree_view_column_set_resizable (c, resizable);
717    gtk_tree_view_column_set_sort_column_id (c, col);
718    gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
719  }
720
721  /* the 'expander' column has a 10-pixel margin on the left
722     that doesn't look quite correct in any of these columns...
723     so create a non-visible column and assign it as the
724     'expander column. */
725  {
726    GtkTreeViewColumn *c = gtk_tree_view_column_new ();
727    gtk_tree_view_column_set_visible (c, FALSE);
728    gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
729    gtk_tree_view_set_expander_column (GTK_TREE_VIEW(v), c);
730  }
731
732  w = sw = gtk_scrolled_window_new (NULL, NULL);
733  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(w),
734                                  GTK_POLICY_NEVER,
735                                  GTK_POLICY_AUTOMATIC);
736  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(w),
737                                       GTK_SHADOW_IN);
738  gtk_container_add (GTK_CONTAINER(w), v);
739
740
741  vbox = gtk_vbox_new (FALSE, GUI_PAD);
742  gtk_container_set_border_width (GTK_CONTAINER(vbox), GUI_PAD_BIG);
743
744    if( webtree )
745        gtk_box_pack_start( GTK_BOX( vbox ), webtree, FALSE, FALSE, 0 );
746
747    //h = gtk_hbox_new (FALSE, GUI_PAD);
748    //gtk_box_pack_start_defaults (GTK_BOX(h), sw);
749    gtk_box_pack_start_defaults( GTK_BOX( vbox ), sw );
750
751    hbox = gtk_hbox_new (FALSE, GUI_PAD);
752        l = gtk_label_new (NULL);
753        gtk_label_set_markup (GTK_LABEL(l), _( "<b>Seeders:</b>" ) );
754        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
755        l = p->seeders_lb = gtk_label_new (NULL);
756        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
757    gtk_box_pack_start_defaults (GTK_BOX(hbox),
758                                 gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
759        l = gtk_label_new (NULL);
760        gtk_label_set_markup (GTK_LABEL(l), _( "<b>Leechers:</b>" ) );
761        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
762        l = p->leechers_lb = gtk_label_new (NULL);
763        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
764    gtk_box_pack_start_defaults (GTK_BOX(hbox),
765                                 gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
766        l = gtk_label_new (NULL);
767        gtk_label_set_markup (GTK_LABEL(l), _( "<b>Times Completed:</b>" ) );
768        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
769        l = p->completed_lb = gtk_label_new (NULL);
770        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
771    gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
772
773  ret = vbox;
774  p->gtor = gtor;
775  p->model = m;
776  p->store = GTK_LIST_STORE(m);
777  g_object_set_data_full (G_OBJECT(ret), "peer-data", p, g_free);
778  return ret;
779}
780
781/****
782*****  INFO TAB
783****/
784
785static void
786refresh_time_lb( GtkWidget * l, time_t t )
787{
788    const char * never = _( "Never" );
789    if( !t )
790        gtk_label_set_text( GTK_LABEL( l ), never );
791    else {
792        char * str = gtr_localtime( t );
793        gtk_label_set_text( GTK_LABEL( l ), str );
794        g_free( str );
795    }
796}
797
798static GtkWidget*
799info_page_new (tr_torrent * tor)
800{
801  int row = 0;
802  GtkWidget *t = hig_workarea_create ();
803  GtkWidget *l, *w, *fr;
804  char *pch;
805  char sizeStr[128];
806  char countStr[128];
807  char buf[256];
808  GtkTextBuffer * b;
809  const tr_info * info = tr_torrentInfo(tor);
810
811  hig_workarea_add_section_title (t, &row, _("Details"));
812
813    g_snprintf( countStr, sizeof( countStr ),
814                ngettext( "%'d Piece", "%'d Pieces", info->pieceCount ),
815                info->pieceCount );
816    tr_strlsize( sizeStr, info->pieceSize, sizeof(sizeStr) );
817    g_snprintf( buf, sizeof( buf ),
818                /* %1$s is number of pieces;
819                   %2$s is how big each piece is */
820                _( "%1$s @ %2$s" ),
821              countStr, sizeStr );
822
823    l = gtk_label_new (buf);
824    hig_workarea_add_row (t, &row, _("Pieces:"), l, NULL);
825
826    l = g_object_new( GTK_TYPE_LABEL, "label", info->hashString, "selectable", TRUE,
827                                      "ellipsize", PANGO_ELLIPSIZE_END, NULL );
828    hig_workarea_add_row (t, &row, _("Hash:"), l, NULL);
829
830    pch = (info->isPrivate )
831      ? _("Private to this tracker -- PEX disabled")
832      : _("Public torrent");
833    l = gtk_label_new (pch);
834    hig_workarea_add_row (t, &row, _("Privacy:"), l, NULL);
835
836    b = gtk_text_buffer_new (NULL);
837    if( info->comment )
838        gtk_text_buffer_set_text (b, info->comment, -1);
839    w = gtk_text_view_new_with_buffer (b);
840    gtk_widget_set_size_request (w, 0u, 100u);
841    gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW(w), GTK_WRAP_WORD); 
842    gtk_text_view_set_editable (GTK_TEXT_VIEW(w), FALSE);
843    fr = gtk_frame_new (NULL);
844    gtk_frame_set_shadow_type (GTK_FRAME(fr), GTK_SHADOW_IN);
845    gtk_container_add (GTK_CONTAINER(fr), w);
846    w = hig_workarea_add_row (t, &row, _("Comment:"), fr, NULL);
847    gtk_misc_set_alignment (GTK_MISC(w), 0.0f, 0.0f);
848
849  hig_workarea_add_section_divider (t, &row);
850  hig_workarea_add_section_title (t, &row, _("Origins"));
851 
852    l = gtk_label_new (*info->creator ? info->creator : _("Unknown"));
853    hig_workarea_add_row (t, &row, _("Creator:"), l, NULL);
854
855    l = gtk_label_new( NULL );
856    refresh_time_lb( l, info->dateCreated );
857    hig_workarea_add_row (t, &row, _("Date:"), l, NULL); 
858
859  hig_workarea_add_section_divider (t, &row);
860  hig_workarea_add_section_title (t, &row, _("Location"));
861
862    l = g_object_new( GTK_TYPE_LABEL, "label", tr_torrentGetDownloadDir( tor ), "selectable", TRUE,
863                                      "ellipsize", PANGO_ELLIPSIZE_END, NULL );
864    hig_workarea_add_row (t, &row, _( "Destination folder:" ), l, NULL); 
865
866    l = g_object_new( GTK_TYPE_LABEL, "label", info->torrent, "selectable", TRUE,
867                                      "ellipsize", PANGO_ELLIPSIZE_END, NULL );
868    hig_workarea_add_row (t, &row, _( "Torrent file:" ), l, NULL); 
869
870  hig_workarea_finish (t, &row);
871  return t;
872}
873
874/****
875*****  ACTIVITY TAB
876****/
877
878typedef struct
879{
880  GtkWidget * state_lb;
881  GtkWidget * progress_lb;
882  GtkWidget * have_lb;
883  GtkWidget * dl_lb;
884  GtkWidget * ul_lb;
885  GtkWidget * failed_lb;
886  GtkWidget * ratio_lb;
887  GtkWidget * err_lb;
888  GtkWidget * swarm_lb;
889  GtkWidget * date_added_lb;
890  GtkWidget * last_activity_lb;
891  GtkWidget * availability_da;
892  TrTorrent * gtor;
893}
894Activity;
895
896static void
897refresh_activity (GtkWidget * top)
898{
899  Activity * a = g_object_get_data (G_OBJECT(top), "activity-data");
900  char *pch;
901  char sizeStr[64];
902  char sizeStr2[64];
903  char buf[128];
904  const tr_stat * stat = tr_torrent_stat( a->gtor );
905  const double complete = stat->percentComplete * 100.0;
906  const double done = stat->percentDone * 100.0;
907
908  pch = tr_torrent_status_str( a->gtor );
909  gtk_label_set_text (GTK_LABEL(a->state_lb), pch);
910  g_free (pch);
911
912  if( (int)complete == (int)done )
913    pch = g_strdup_printf( _( "%.1f%%" ), complete );
914  else
915    /* %1$.1f is percent of how much of what we want's been downloaded,
916       %2$.1f is percent of how much of the whole torrent we've downloaded */
917    pch = g_strdup_printf( _( "%1$.1f%% (%2$.1f%% selected)" ), complete, done );
918  gtk_label_set_text (GTK_LABEL(a->progress_lb), pch);
919  g_free (pch);
920
921  tr_strlsize( sizeStr,  stat->haveValid + stat->haveUnchecked, sizeof(sizeStr) );
922  tr_strlsize( sizeStr2, stat->haveValid,                       sizeof(sizeStr2) );
923  /* %1$s is total size of what we've saved to disk
924     %2$s is how much of it's passed the checksum test */
925  g_snprintf( buf, sizeof(buf), _("%1$s (%2$s verified)"), sizeStr, sizeStr2 );
926  gtk_label_set_text( GTK_LABEL( a->have_lb ), buf );
927
928  tr_strlsize( sizeStr, stat->downloadedEver, sizeof(sizeStr) );
929  gtk_label_set_text( GTK_LABEL(a->dl_lb), sizeStr );
930
931  tr_strlsize( sizeStr, stat->uploadedEver, sizeof(sizeStr) );
932  gtk_label_set_text( GTK_LABEL(a->ul_lb), sizeStr );
933
934  tr_strlsize( sizeStr, stat->corruptEver, sizeof( sizeStr ) );
935  gtk_label_set_text( GTK_LABEL( a->failed_lb ), sizeStr );
936
937  tr_strlratio( buf, stat->ratio, sizeof( buf ) );
938  gtk_label_set_text( GTK_LABEL( a->ratio_lb ), buf );
939
940  tr_strlspeed( buf, stat->swarmSpeed, sizeof(buf) );
941  gtk_label_set_text (GTK_LABEL(a->swarm_lb), buf );
942
943  gtk_label_set_text (GTK_LABEL(a->err_lb),
944                      *stat->errorString ? stat->errorString : _("None"));
945
946  refresh_time_lb( a->date_added_lb, stat->addedDate );
947
948  refresh_time_lb( a->last_activity_lb, stat->activityDate );
949
950#ifdef SHOW_PIECES
951  if (GDK_IS_DRAWABLE (a->availability_da->window))
952    refresh_pieces (a->availability_da, NULL, a->gtor);
953#endif
954}
955 
956
957static GtkWidget*
958activity_page_new (TrTorrent * gtor)
959{
960  Activity * a = g_new (Activity, 1);
961  int row = 0;
962  GtkWidget *t = hig_workarea_create ();
963  GtkWidget *l;
964
965  a->gtor = gtor;
966
967  hig_workarea_add_section_title (t, &row, _("Transfer"));
968
969    l = a->state_lb = gtk_label_new (NULL);
970    hig_workarea_add_row (t, &row, _("State:"), l, NULL);
971
972    l = a->progress_lb = gtk_label_new (NULL);
973    hig_workarea_add_row (t, &row, _("Progress:"), l, NULL);
974
975    l = a->have_lb = gtk_label_new (NULL);
976    /* "Have" refers to how much of the torrent we have */
977    hig_workarea_add_row (t, &row, _("Have:"), l, NULL);
978
979    l = a->dl_lb = gtk_label_new (NULL);
980    hig_workarea_add_row (t, &row, _("Downloaded:"), l, NULL);
981
982    l = a->ul_lb = gtk_label_new (NULL);
983    hig_workarea_add_row (t, &row, _("Uploaded:"), l, NULL);
984
985    /* how much downloaded data was corrupt */
986    l = a->failed_lb = gtk_label_new (NULL);
987    hig_workarea_add_row (t, &row, _("Failed DL:"), l, NULL);
988
989    l = a->ratio_lb = gtk_label_new (NULL);
990    hig_workarea_add_row (t, &row, _("Ratio:"), l, NULL);
991
992    l = a->swarm_lb = gtk_label_new (NULL);
993    hig_workarea_add_row (t, &row, _("Swarm rate:"), l, NULL);
994
995    l = a->err_lb = gtk_label_new (NULL);
996    hig_workarea_add_row (t, &row, _("Error:"), l, NULL);
997
998#ifdef SHOW_PIECES
999  hig_workarea_add_section_divider (t, &row);
1000  hig_workarea_add_section_title (t, &row, _("Completion"));
1001
1002    w = a->availability_da = gtk_drawing_area_new ();
1003    gtk_widget_set_size_request (w, 0u, 100u);
1004    g_object_set_data (G_OBJECT(w), "draw-mode", GINT_TO_POINTER(DRAW_PROG));
1005    g_signal_connect (w, "expose-event", G_CALLBACK(refresh_pieces), gtor);
1006    hig_workarea_add_wide_control( t, &row, w );
1007#endif
1008
1009  hig_workarea_add_section_divider (t, &row);
1010  hig_workarea_add_section_title (t, &row, _("Dates"));
1011
1012    l = a->date_added_lb = gtk_label_new (NULL);
1013    hig_workarea_add_row (t, &row, _("Started at:"), l, NULL);
1014
1015    l = a->last_activity_lb = gtk_label_new (NULL);
1016    hig_workarea_add_row (t, &row, _("Last activity at:"), l, NULL);
1017
1018  hig_workarea_add_section_divider (t, &row);
1019  hig_workarea_finish (t, &row);
1020  g_object_set_data_full (G_OBJECT(t), "activity-data", a, g_free);
1021  return t;
1022}
1023
1024
1025/****
1026*****  OPTIONS
1027****/
1028
1029static void
1030speed_toggled_cb( GtkToggleButton * tb, gpointer gtor, int up_or_down )
1031{
1032  tr_torrent * tor = tr_torrent_handle (gtor);
1033  gboolean b = gtk_toggle_button_get_active(tb);
1034  tr_torrentSetSpeedMode( tor, up_or_down, b ? TR_SPEEDLIMIT_SINGLE
1035                                             : TR_SPEEDLIMIT_GLOBAL );
1036}
1037static void
1038ul_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
1039{
1040  speed_toggled_cb( tb, gtor, TR_UP );
1041}
1042static void
1043dl_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
1044{
1045  speed_toggled_cb( tb, gtor, TR_DOWN );
1046}
1047
1048
1049static void
1050sensitize_from_check_cb (GtkToggleButton *toggle, gpointer w)
1051{
1052  gtk_widget_set_sensitive (GTK_WIDGET(w),
1053                            gtk_toggle_button_get_active(toggle));
1054}
1055
1056static void
1057setSpeedLimit( GtkSpinButton* spin, gpointer gtor, int up_or_down )
1058{
1059  tr_torrent * tor = tr_torrent_handle (gtor);
1060  int kb_sec = gtk_spin_button_get_value_as_int (spin);
1061  tr_torrentSetSpeedLimit( tor, up_or_down, kb_sec );
1062}
1063static void
1064ul_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
1065{
1066  setSpeedLimit( spin, gtor, TR_UP );
1067}
1068static void
1069dl_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
1070{
1071  setSpeedLimit( spin, gtor, TR_DOWN );
1072}
1073
1074static void
1075max_peers_spun_cb( GtkSpinButton * spin, gpointer gtor )
1076{
1077  const uint16_t n = gtk_spin_button_get_value( spin );
1078  tr_torrentSetPeerLimit( tr_torrent_handle( gtor ), n );
1079}
1080
1081static GtkWidget*
1082options_page_new ( TrTorrent * gtor )
1083{
1084  uint16_t maxConnectedPeers;
1085  int i, row;
1086  gboolean b;
1087  GtkAdjustment *a;
1088  GtkWidget *t, *w, *tb;
1089  tr_torrent * tor = tr_torrent_handle (gtor);
1090
1091  row = 0;
1092  t = hig_workarea_create ();
1093  hig_workarea_add_section_title (t, &row, _("Bandwidth") );
1094
1095    tb = gtk_check_button_new_with_mnemonic (_("Limit _download speed (KB/s):"));
1096    b = tr_torrentGetSpeedMode(tor,TR_DOWN) == TR_SPEEDLIMIT_SINGLE;
1097    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), b );
1098    g_signal_connect (tb, "toggled", G_CALLBACK(dl_speed_toggled_cb), gtor);
1099
1100    i = tr_torrentGetSpeedLimit( tor, TR_DOWN );
1101    a = (GtkAdjustment*) gtk_adjustment_new (i, 0.0, G_MAXDOUBLE, 1, 1, 1);
1102    w = gtk_spin_button_new (a, 1, 0);
1103    g_signal_connect (w, "value-changed", G_CALLBACK(dl_speed_spun_cb), gtor);
1104    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1105    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1106    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1107
1108    tb = gtk_check_button_new_with_mnemonic (_("Limit _upload speed (KB/s):"));
1109    b = tr_torrentGetSpeedMode(tor,TR_UP) == TR_SPEEDLIMIT_SINGLE;
1110    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), b );
1111    g_signal_connect (tb, "toggled", G_CALLBACK(ul_speed_toggled_cb), gtor);
1112
1113    i = tr_torrentGetSpeedLimit( tor, TR_UP );
1114    a = (GtkAdjustment*) gtk_adjustment_new (i, 0.0, G_MAXDOUBLE, 1, 1, 1);
1115    w = gtk_spin_button_new (a, 1, 0);
1116    g_signal_connect (w, "value-changed", G_CALLBACK(ul_speed_spun_cb), gtor);
1117    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1118    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1119    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1120
1121  hig_workarea_add_section_divider (t, &row);
1122  hig_workarea_add_section_title (t, &row, _("Peer Connections"));
1123
1124    maxConnectedPeers = tr_torrentGetPeerLimit( tor );
1125    w = gtk_spin_button_new_with_range( 1, 3000, 5 );
1126    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), maxConnectedPeers );
1127    hig_workarea_add_row( t, &row, _( "_Maximum peers:" ), w, w );
1128    g_signal_connect( w, "value-changed", G_CALLBACK( max_peers_spun_cb ), gtor );
1129
1130  hig_workarea_finish (t, &row);
1131  return t;
1132}
1133
1134static void
1135refresh_options (GtkWidget * top UNUSED)
1136{
1137}
1138
1139/****
1140*****  TRACKER
1141****/
1142
1143#define TRACKER_PAGE "tracker-page"
1144
1145struct tracker_page
1146{
1147    TrTorrent * gtor;
1148
1149    GtkTreeView * view;
1150    GtkListStore * store;
1151    GtkTreeSelection * sel;
1152
1153    GtkWidget * add_button;
1154    GtkWidget * remove_button;
1155    GtkWidget * save_button;
1156    GtkWidget * revert_button;
1157
1158    GtkWidget * last_scrape_time_lb;
1159    GtkWidget * last_scrape_response_lb;
1160    GtkWidget * next_scrape_countdown_lb;
1161
1162    GtkWidget * last_announce_time_lb;
1163    GtkWidget * last_announce_response_lb;
1164    GtkWidget * next_announce_countdown_lb;
1165    GtkWidget * manual_announce_countdown_lb;
1166};
1167
1168GtkWidget*
1169tracker_page_new( TrTorrent * gtor )
1170{
1171    GtkWidget * t;
1172    GtkWidget * l;
1173    GtkWidget * w;
1174    int row = 0;
1175    const char * s;
1176    struct tracker_page * page = g_new0( struct tracker_page, 1 );
1177    const tr_info * info = tr_torrent_info (gtor);
1178
1179    page->gtor = gtor;
1180
1181    t = hig_workarea_create( );
1182    hig_workarea_add_section_title( t, &row, _( "Trackers" ) );
1183
1184        w = tracker_list_new( gtor );
1185        hig_workarea_add_wide_control( t, &row, w );
1186
1187    hig_workarea_add_section_divider( t, &row );
1188    hig_workarea_add_section_title( t, &row, _( "Scrape" ) );
1189
1190        s = _( "Last scrape at:" );
1191        l = gtk_label_new( NULL );
1192        page->last_scrape_time_lb = l;
1193        hig_workarea_add_row( t, &row, s, l, NULL );
1194
1195        s = _( "Tracker responded:");
1196        l = gtk_label_new( NULL );
1197        page->last_scrape_response_lb = l;
1198        hig_workarea_add_row( t, &row, s, l, NULL );
1199
1200        s = _( "Next scrape in:" );
1201        l = gtk_label_new( NULL );
1202        page->next_scrape_countdown_lb = l;
1203        hig_workarea_add_row( t, &row, s, l, NULL );
1204
1205    hig_workarea_add_section_divider( t, &row );
1206    hig_workarea_add_section_title( t, &row, _( "Announce" ) );
1207
1208        l = gtk_label_new( info->trackers[0].announce );
1209        gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
1210        hig_workarea_add_row (t, &row, _( "Tracker:" ), l, NULL);
1211
1212        s = _( "Last announce at:" );
1213        l = gtk_label_new( NULL );
1214        page->last_announce_time_lb = l;
1215        hig_workarea_add_row( t, &row, s, l, NULL );
1216
1217        s = _( "Tracker responded:");
1218        l = gtk_label_new( NULL );
1219        page->last_announce_response_lb = l;
1220        hig_workarea_add_row( t, &row, s, l, NULL );
1221
1222        s = _( "Next announce in:" );
1223        l = gtk_label_new( NULL );
1224        page->next_announce_countdown_lb = l;
1225        hig_workarea_add_row( t, &row, s, l, NULL );
1226
1227        /* how long until the tracker will honor user
1228         * pressing the "ask for more peers" button */
1229        s = _( "Manual announce allowed in:" );
1230        l = gtk_label_new( NULL );
1231        page->manual_announce_countdown_lb = l;
1232        hig_workarea_add_row( t, &row, s, l, NULL );
1233
1234    hig_workarea_finish( t, &row );
1235    g_object_set_data_full( G_OBJECT( t ), TRACKER_PAGE, page, g_free );
1236    return t;
1237}
1238
1239static void
1240refresh_countdown_lb( GtkWidget * w, time_t t,
1241                      const char * countdown_done )
1242{
1243    const time_t now = time( NULL );
1244    GtkLabel * l = GTK_LABEL( w );
1245
1246    if( t == 1 )
1247        gtk_label_set_text( l, _( "In progress" ) );
1248    else if( t < now )
1249        gtk_label_set_text( l, countdown_done );
1250    else {
1251        char buf[1024];
1252        const int seconds = t - now;
1253        tr_strltime( buf, seconds, sizeof( buf ) );
1254        gtk_label_set_text( l, buf );
1255    }
1256}
1257
1258static void
1259refresh_tracker( GtkWidget * w )
1260{
1261    GtkWidget * l;
1262    time_t t;
1263    struct tracker_page * page = g_object_get_data( G_OBJECT( w ), TRACKER_PAGE );
1264    const tr_stat * torStat = tr_torrent_stat( page->gtor );
1265
1266    l = page->last_scrape_time_lb;
1267    t = torStat->lastScrapeTime;
1268    refresh_time_lb( l, t );
1269
1270    l = page->last_scrape_response_lb;
1271    gtk_label_set_text( GTK_LABEL( l ), torStat->scrapeResponse );
1272
1273    l = page->next_scrape_countdown_lb;
1274    t = torStat->nextScrapeTime;
1275    refresh_countdown_lb( l, t, _( "Never" ) );
1276
1277    l = page->last_announce_time_lb;
1278    t = torStat->lastAnnounceTime;
1279    refresh_time_lb( l, t );
1280
1281    l = page->last_announce_response_lb;
1282    gtk_label_set_text( GTK_LABEL( l ), torStat->announceResponse );
1283
1284    l = page->next_announce_countdown_lb;
1285    t = torStat->nextAnnounceTime;
1286    refresh_countdown_lb( l, t, _( "Never" ) );
1287
1288    l = page->manual_announce_countdown_lb;
1289    t = torStat->manualAnnounceTime;
1290    refresh_countdown_lb( l, t, _( "Now" ) );
1291}
1292
1293/****
1294*****  DIALOG
1295****/
1296
1297static void
1298torrent_destroyed (gpointer dialog, GObject * dead_torrent UNUSED)
1299{
1300  gtk_widget_destroy (GTK_WIDGET(dialog));
1301}
1302
1303static void
1304remove_tag (gpointer tag)
1305{
1306  g_source_remove (GPOINTER_TO_UINT(tag)); /* stop the periodic refresh */
1307}
1308
1309static void
1310response_cb (GtkDialog *dialog, int response UNUSED, gpointer gtor)
1311{
1312  g_object_weak_unref (G_OBJECT(gtor), torrent_destroyed, dialog);
1313  gtk_widget_destroy (GTK_WIDGET(dialog));
1314}
1315
1316static gboolean
1317periodic_refresh (gpointer data)
1318{
1319  refresh_tracker   (g_object_get_data (G_OBJECT(data), "tracker-top"));
1320  refresh_peers     (g_object_get_data (G_OBJECT(data), "peers-top"));
1321  refresh_activity  (g_object_get_data (G_OBJECT(data), "activity-top"));
1322  refresh_options   (g_object_get_data (G_OBJECT(data), "options-top"));
1323  return TRUE;
1324}
1325
1326GtkWidget*
1327torrent_inspector_new ( GtkWindow * parent, TrTorrent * gtor )
1328{
1329  guint tag;
1330  GtkWidget *d, *n, *w;
1331  tr_torrent * tor = tr_torrent_handle (gtor);
1332  char sizeStr[64];
1333  char title[512];
1334  const tr_info * info = tr_torrent_info (gtor);
1335
1336  /* create the dialog */
1337  tr_strlsize( sizeStr, info->totalSize, sizeof(sizeStr) );
1338  /* %1$s is torrent name
1339     %2$s its file size */
1340  g_snprintf( title, sizeof(title), _( "Details for %1$s (%2$s)" ), info->name, sizeStr );
1341  d = gtk_dialog_new_with_buttons (title, parent, 0,
1342                                   GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1343                                   NULL);
1344  gtk_window_set_role (GTK_WINDOW(d), "tr-info" );
1345  g_signal_connect (d, "response", G_CALLBACK (response_cb), gtor);
1346  gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
1347  gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1348  g_object_weak_ref (G_OBJECT(gtor), torrent_destroyed, d);
1349 
1350
1351  /* add the notebook */
1352  n = gtk_notebook_new ();
1353  gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1354
1355  w = activity_page_new (gtor);
1356  g_object_set_data (G_OBJECT(d), "activity-top", w);
1357  gtk_notebook_append_page (GTK_NOTEBOOK(n), w, 
1358                            gtk_label_new (_("Activity")));
1359
1360  w = peer_page_new (gtor);
1361  g_object_set_data (G_OBJECT(d), "peers-top", w);
1362  gtk_notebook_append_page (GTK_NOTEBOOK(n),  w,
1363                            gtk_label_new (_("Peers")));
1364
1365  w = tracker_page_new( gtor );
1366  g_object_set_data( G_OBJECT( d ), "tracker-top", w );
1367  gtk_notebook_append_page( GTK_NOTEBOOK( n ), w,
1368                            gtk_label_new( _( "Tracker" ) ) );
1369
1370  gtk_notebook_append_page (GTK_NOTEBOOK(n),
1371                            info_page_new (tor),
1372                            gtk_label_new (_("Information")));
1373
1374  w = file_list_new( gtor );
1375  gtk_container_set_border_width( GTK_CONTAINER( w ), GUI_PAD_BIG );
1376  g_object_set_data (G_OBJECT(d), "files-top", w);
1377  gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
1378                            gtk_label_new (_("Files")));
1379
1380  w = options_page_new (gtor);
1381  g_object_set_data (G_OBJECT(d), "options-top", w);
1382  gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
1383                            gtk_label_new (_("Options")));
1384
1385  gtk_box_pack_start_defaults (GTK_BOX(GTK_DIALOG(d)->vbox), n);
1386
1387  tag = g_timeout_add (UPDATE_INTERVAL_MSEC, periodic_refresh, d);
1388  g_object_set_data_full (G_OBJECT(d), "tag",
1389                          GUINT_TO_POINTER(tag), remove_tag);
1390
1391  /* return the results */
1392  periodic_refresh (d);
1393  gtk_widget_show_all (GTK_DIALOG(d)->vbox);
1394  return d;
1395}
Note: See TracBrowser for help on using the repository browser.