source: trunk/gtk/details.c @ 6020

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

(gtk) #930: Patch to reorder format string args for translation

  • Property svn:keywords set to Date Rev Author Id
File size: 41.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 6020 2008-06-03 20:13:22Z 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  PEER_COL_ADDRESS,
238  PEER_COL_CLIENT,
239  PEER_COL_PROGRESS,
240  PEER_COL_IS_ENCRYPTED,
241  PEER_COL_DOWNLOAD_RATE,
242  PEER_COL_UPLOAD_RATE,
243  PEER_COL_STATUS,
244  N_PEER_COLS
245};
246
247static const char* peer_column_names[N_PEER_COLS] =
248{
249  N_("Address"),
250  N_("Client"),
251  /* 'percent done' column header. terse to keep the column narrow. */
252  N_("%"),
253  " ",
254  /* 'download speed' column header. terse to keep the column narrow. */
255  N_("Down"),
256  /* 'upload speed' column header.  terse to keep the column narrow. */
257  N_("Up"),
258  N_("Status")
259};
260
261static int compare_peers (const void * a, const void * b)
262{
263  const tr_peer_stat * pa = a;
264  const tr_peer_stat * pb = b;
265  return strcmp (pa->addr, pb->addr);
266}
267static int compare_addr_to_peer (const void * a, const void * b)
268{
269  const char * addr = (const char *) a;
270  const tr_peer_stat * peer = b;
271  return strcmp (addr, peer->addr);
272}
273
274static void
275peer_row_set (GtkTreeStore        * store,
276              GtkTreeIter         * iter,
277              const tr_peer_stat  * peer)
278{
279  const char * client = peer->client;
280
281  if (!client || !strcmp(client,"Unknown Client"))
282    client = " ";
283
284  gtk_tree_store_set (store, iter,
285                      PEER_COL_ADDRESS, peer->addr,
286                      PEER_COL_CLIENT, client,
287                      PEER_COL_IS_ENCRYPTED, peer->isEncrypted,
288                      PEER_COL_PROGRESS, (int)(100.0*peer->progress),
289                      PEER_COL_DOWNLOAD_RATE, peer->downloadFromRate,
290                      PEER_COL_UPLOAD_RATE, peer->uploadToRate,
291                      PEER_COL_STATUS, peer->flagStr,
292                      -1);
293}
294
295static void
296append_peers_to_model (GtkTreeStore        * store,
297                       const tr_peer_stat  * peers,
298                       int                   n_peers)
299{
300  int i;
301  for (i=0; i<n_peers; ++i) {
302    GtkTreeIter iter;
303    gtk_tree_store_append (store, &iter, NULL);
304    peer_row_set (store, &iter, &peers[i]);
305  }
306}
307
308static GtkTreeModel*
309peer_model_new (tr_torrent * tor)
310{
311  GtkTreeStore * m = gtk_tree_store_new (N_PEER_COLS,
312                                         G_TYPE_STRING,   /* addr */
313                                         G_TYPE_STRING,   /* client */
314                                         G_TYPE_INT,      /* progress [0..100] */
315                                         G_TYPE_BOOLEAN,  /* isEncrypted */
316                                         G_TYPE_FLOAT,    /* downloadFromRate */
317                                         G_TYPE_FLOAT,    /* uploadToRate */
318                                         G_TYPE_STRING ); /* flagString */
319
320  int n_peers = 0;
321  tr_peer_stat * peers = tr_torrentPeers (tor, &n_peers);
322  qsort (peers, n_peers, sizeof(tr_peer_stat), compare_peers);
323  append_peers_to_model (m, peers, n_peers);
324  tr_torrentPeersFree( peers, 0 );
325  return GTK_TREE_MODEL (m);
326}
327
328static void
329render_encrypted (GtkTreeViewColumn  * column UNUSED,
330                  GtkCellRenderer    * renderer,
331                  GtkTreeModel       * tree_model,
332                  GtkTreeIter        * iter,
333                  gpointer             data UNUSED)
334{
335  gboolean is_encrypted = FALSE;
336  gtk_tree_model_get (tree_model, iter, PEER_COL_IS_ENCRYPTED, &is_encrypted,
337                                        -1);
338  g_object_set (renderer, "xalign", (gfloat)0.0,
339                          "yalign", (gfloat)0.5,
340                          "stock-id", (is_encrypted ? "transmission-lock" : NULL),
341                          NULL);
342}
343
344static void
345render_ul_rate (GtkTreeViewColumn  * column UNUSED,
346                GtkCellRenderer    * renderer,
347                GtkTreeModel       * tree_model,
348                GtkTreeIter        * iter,
349                gpointer             data UNUSED)
350{
351  float rate = 0.0;
352  gtk_tree_model_get (tree_model, iter, PEER_COL_UPLOAD_RATE, &rate, -1);
353  if( rate < 0.01 )
354    g_object_set (renderer, "text", "", NULL);
355  else {
356    char speedStr[64];
357    tr_strlspeed( speedStr, rate, sizeof(speedStr) );
358    g_object_set( renderer, "text", speedStr, NULL );
359  }
360}
361
362static void
363render_dl_rate (GtkTreeViewColumn  * column UNUSED,
364                GtkCellRenderer    * renderer,
365                GtkTreeModel       * tree_model,
366                GtkTreeIter        * iter,
367                gpointer             data UNUSED)
368{
369  float rate = 0.0;
370  gtk_tree_model_get (tree_model, iter, PEER_COL_DOWNLOAD_RATE, &rate, -1);
371  if( rate < 0.01 )
372    g_object_set (renderer, "text", "", NULL);
373  else {
374    char speedStr[64];
375    tr_strlspeed( speedStr, rate, sizeof(speedStr) );
376    g_object_set( renderer, "text", speedStr, NULL );
377  }
378}
379
380static void
381render_client (GtkTreeViewColumn   * column UNUSED,
382               GtkCellRenderer     * renderer,
383               GtkTreeModel        * tree_model,
384               GtkTreeIter         * iter,
385               gpointer              data UNUSED)
386{
387  char * client = NULL;
388  gtk_tree_model_get (tree_model, iter, PEER_COL_CLIENT, &client,
389                                        -1);
390  g_object_set (renderer, "text", (client ? client : ""), NULL);
391  g_free (client);
392}
393
394typedef struct
395{
396  TrTorrent * gtor;
397  GtkTreeModel * model; /* same object as store, but recast */
398  GtkTreeStore * store; /* same object as model, but recast */
399  GtkWidget * completeness;
400  GtkWidget * seeders_lb;
401  GtkWidget * leechers_lb;
402  GtkWidget * completed_lb;
403}
404PeerData;
405
406static void
407fmtpeercount (GtkWidget * l, int count)
408{
409  if( 0 > count ) {
410    gtk_label_set_text( GTK_LABEL(l), "?" );
411  } else {
412    char str[16];
413    g_snprintf( str, sizeof str, "%'d", count );
414    gtk_label_set_text( GTK_LABEL(l), str );
415  }
416}
417
418static void
419refresh_peers (GtkWidget * top)
420{
421  int i;
422  int n_peers;
423  GtkTreeIter iter;
424  PeerData * p = (PeerData*) g_object_get_data (G_OBJECT(top), "peer-data");
425  tr_torrent * tor = tr_torrent_handle ( p->gtor );
426  GtkTreeModel * model = p->model;
427  GtkTreeStore * store = p->store;
428  tr_peer_stat * peers;
429  const tr_stat * stat = tr_torrent_stat( p->gtor );
430
431  /**
432  ***  merge the peer diffs into the tree model.
433  ***
434  ***  this is more complicated than creating a new model,
435  ***  but is also (a) more efficient and (b) doesn't undo
436  ***  the view's visible area and sorting on every refresh.
437  **/
438
439  n_peers = 0;
440  peers = tr_torrentPeers (tor, &n_peers);
441  qsort (peers, n_peers, sizeof(tr_peer_stat), compare_peers);
442
443  i = 0;
444  if (gtk_tree_model_get_iter_first (model, &iter)) do
445  {
446    char * addr = NULL;
447    tr_peer_stat * peer = NULL;
448    gtk_tree_model_get (model, &iter, PEER_COL_ADDRESS, &addr, -1);
449    peer = bsearch (addr, peers, n_peers, sizeof(tr_peer_stat),
450                    compare_addr_to_peer);
451    g_free (addr);
452
453    if (peer) /* update a pre-existing row */
454    {
455      const int pos = peer - peers;
456      const int n_rhs = n_peers - (pos+1);
457      g_assert (n_rhs >= 0);
458
459      peer_row_set (store, &iter, peer);
460
461      /* remove it from the tr_peer_stat list */
462      g_memmove (peer, peer+1, sizeof(tr_peer_stat)*n_rhs);
463      --n_peers;
464    }
465    else if (!gtk_tree_store_remove (store, &iter))
466      break; /* we removed the model's last item */
467  }
468  while (gtk_tree_model_iter_next (model, &iter));
469
470  append_peers_to_model (store, peers, n_peers);  /* all these are new */
471
472#ifdef SHOW_PIECES
473  if (GDK_IS_DRAWABLE (p->completeness->window))
474    refresh_pieces (p->completeness, NULL, p->gtor);
475#endif
476
477  fmtpeercount (p->seeders_lb, stat->seeders);
478  fmtpeercount (p->leechers_lb, stat->leechers);
479  fmtpeercount (p->completed_lb, stat->completedFromTracker );
480
481  free( peers );
482}
483
484#if GTK_CHECK_VERSION(2,12,0)
485static gboolean
486onPeerViewQueryTooltip( GtkWidget  * widget,
487                        gint         x,
488                        gint         y,
489                        gboolean     keyboard_tip,
490                        GtkTooltip * tooltip,
491                        gpointer     user_data UNUSED )
492{
493    gboolean show_tip = FALSE;
494    GtkTreeModel * model;
495    GtkTreeIter iter;
496
497    if( gtk_tree_view_get_tooltip_context( GTK_TREE_VIEW( widget ),
498                                           &x, &y, keyboard_tip,
499                                           &model, NULL, &iter ) )
500    {
501        const char * pch;
502        char * str = NULL;
503        GString * gstr = g_string_new( NULL );
504        gtk_tree_model_get( model, &iter, PEER_COL_STATUS, &str, -1 );
505        for( pch=str; pch && *pch; ++pch ) {
506            const char * txt = NULL;
507            switch( *pch ) {
508                case 'O': txt = _( "Optimistic unchoke" ); break;
509                case 'D': txt = _( "Downloading from peer" ); break;
510                case 'd': txt = _( "We'd download from peer if they'd let us" ); break;
511                case 'U': txt = _( "Uploading to peer" ); break;
512                case 'u': txt = _( "We'd upload to peer if they'd ask us" ); break;
513                case 'K': txt = _( "Peer has unchoked us, but we're not interested" ); break;
514                case '?': txt = _( "We unchoked the peer, but they're not interested" ); break;
515                case 'E': txt = _( "Encrypted connection" ); break;
516                case 'X': txt = _( "Peer was discovered through Peer Exchange (PEX)" ); break;
517                case 'I': txt = _( "Peer is an incoming connection" ); break;
518            }
519            if( txt )
520                g_string_append_printf( gstr, "%c: %s\n", *pch, txt );
521        }
522        if( gstr->len ) /* remove the last linefeed */
523            g_string_set_size( gstr, gstr->len-1 );
524        gtk_tooltip_set_text( tooltip, gstr->str );
525        g_string_free( gstr, TRUE );
526        g_free( str );
527        show_tip = TRUE;
528    }
529
530    return show_tip;
531
532}
533#endif
534
535static GtkWidget*
536peer_page_new ( TrTorrent * gtor )
537{
538  guint i;
539  GtkTreeModel *m;
540  GtkWidget *h, *v, *w, *ret, *sw, *l, *vbox, *hbox;
541  tr_torrent * tor = tr_torrent_handle (gtor);
542  PeerData * p = g_new (PeerData, 1);
543
544  /* TODO: make this configurable? */
545  int view_columns[] = { PEER_COL_IS_ENCRYPTED,
546                         PEER_COL_UPLOAD_RATE,
547                         PEER_COL_DOWNLOAD_RATE,
548                         PEER_COL_PROGRESS,
549                         PEER_COL_STATUS,
550                         PEER_COL_ADDRESS,
551                         PEER_COL_CLIENT };
552
553  m  = peer_model_new (tor);
554  v = GTK_WIDGET( g_object_new( GTK_TYPE_TREE_VIEW,
555                                "model", m,
556                                "rules-hint", TRUE,
557#if GTK_CHECK_VERSION(2,12,0)
558                                "has-tooltip", TRUE,
559#endif
560                                NULL ) );
561#if GTK_CHECK_VERSION(2,12,0)
562  g_signal_connect( v, "query-tooltip",
563                    G_CALLBACK(onPeerViewQueryTooltip), NULL );
564#endif
565  gtk_widget_set_size_request( v, 550, 0 );
566  g_object_unref (G_OBJECT(m));
567
568  for (i=0; i<G_N_ELEMENTS(view_columns); ++i)
569  {
570    const int col = view_columns[i];
571    const char * t = _(peer_column_names[col]);
572    gboolean resizable = TRUE;
573    GtkTreeViewColumn * c;
574    GtkCellRenderer * r;
575
576    switch (col)
577    {
578      case PEER_COL_ADDRESS:
579        r = gtk_cell_renderer_text_new ();
580        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
581        break;
582
583      case PEER_COL_CLIENT:
584        r = gtk_cell_renderer_text_new ();
585        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
586        gtk_tree_view_column_set_cell_data_func (c, r, render_client,
587                                                 NULL, NULL);
588        break;
589
590      case PEER_COL_PROGRESS:
591        r = gtk_cell_renderer_progress_new ();
592        c = gtk_tree_view_column_new_with_attributes (t, r, "value", PEER_COL_PROGRESS, NULL);
593        break;
594
595      case PEER_COL_IS_ENCRYPTED:
596        resizable = FALSE;
597        r = gtk_cell_renderer_pixbuf_new ();
598        c = gtk_tree_view_column_new_with_attributes (t, r, NULL);
599        gtk_tree_view_column_set_sizing (c, GTK_TREE_VIEW_COLUMN_FIXED);
600        gtk_tree_view_column_set_fixed_width (c, 20);
601        gtk_tree_view_column_set_cell_data_func (c, r, render_encrypted,
602                                                 NULL, NULL);
603        break;
604
605      case PEER_COL_DOWNLOAD_RATE:
606        r = gtk_cell_renderer_text_new ();
607        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
608        gtk_tree_view_column_set_cell_data_func (c, r, render_dl_rate,
609                                                 NULL, NULL);
610        break;
611
612      case PEER_COL_UPLOAD_RATE:
613        r = gtk_cell_renderer_text_new ();
614        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
615        gtk_tree_view_column_set_cell_data_func (c, r, render_ul_rate,
616                                                 NULL, NULL);
617        break;
618
619      case PEER_COL_STATUS:
620        r = gtk_cell_renderer_text_new( );
621        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
622        break;
623
624      default:
625        abort ();
626    }
627
628    gtk_tree_view_column_set_resizable (c, resizable);
629    gtk_tree_view_column_set_sort_column_id (c, col);
630    gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
631  }
632
633  /* the 'expander' column has a 10-pixel margin on the left
634     that doesn't look quite correct in any of these columns...
635     so create a non-visible column and assign it as the
636     'expander column. */
637  {
638    GtkTreeViewColumn *c = gtk_tree_view_column_new ();
639    gtk_tree_view_column_set_visible (c, FALSE);
640    gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
641    gtk_tree_view_set_expander_column (GTK_TREE_VIEW(v), c);
642  }
643
644  w = sw = gtk_scrolled_window_new (NULL, NULL);
645  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(w),
646                                  GTK_POLICY_NEVER,
647                                  GTK_POLICY_AUTOMATIC);
648  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(w),
649                                       GTK_SHADOW_IN);
650  gtk_container_add (GTK_CONTAINER(w), v);
651
652
653  vbox = gtk_vbox_new (FALSE, GUI_PAD);
654  gtk_container_set_border_width (GTK_CONTAINER(vbox), GUI_PAD_BIG);
655
656#ifdef SHOW_PIECES
657    l = gtk_label_new (NULL);
658    gtk_misc_set_alignment (GTK_MISC(l), 0.0f, 0.5f);
659    gtk_label_set_markup (GTK_LABEL(l), _( "<b>Availability</b>" ) );
660    gtk_box_pack_start (GTK_BOX(vbox), l, FALSE, FALSE, 0);
661
662    w = da = p->completeness = gtk_drawing_area_new ();
663    gtk_widget_set_size_request (w, 0u, 100u);
664    g_object_set_data (G_OBJECT(w), "draw-mode", GINT_TO_POINTER(DRAW_AVAIL));
665    g_signal_connect (w, "expose-event", G_CALLBACK(refresh_pieces), gtor);
666
667    h = gtk_hbox_new (FALSE, GUI_PAD);
668    w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
669    gtk_widget_set_size_request (w, GUI_PAD_BIG, 0);
670    gtk_box_pack_start (GTK_BOX(h), w, FALSE, FALSE, 0);
671    gtk_box_pack_start_defaults (GTK_BOX(h), da);
672    gtk_box_pack_start (GTK_BOX(vbox), h, FALSE, FALSE, 0);
673
674    /* a small vertical spacer */
675    w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
676    gtk_widget_set_size_request (w, 0u, GUI_PAD);
677    gtk_box_pack_start (GTK_BOX(vbox), w, FALSE, FALSE, 0);
678
679    l = gtk_label_new (NULL);
680    gtk_misc_set_alignment (GTK_MISC(l), 0.0f, 0.5f);
681    gtk_label_set_markup (GTK_LABEL(l), _( "<b>Connected Peers</b>" ) );
682    gtk_box_pack_start (GTK_BOX(vbox), l, FALSE, FALSE, 0);
683#endif
684
685    h = gtk_hbox_new (FALSE, GUI_PAD);
686    gtk_box_pack_start_defaults (GTK_BOX(h), sw);
687    gtk_box_pack_start_defaults (GTK_BOX(vbox), h);
688
689    hbox = gtk_hbox_new (FALSE, GUI_PAD);
690        l = gtk_label_new (NULL);
691        gtk_label_set_markup (GTK_LABEL(l), _( "<b>Seeders:</b>" ) );
692        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
693        l = p->seeders_lb = gtk_label_new (NULL);
694        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
695    gtk_box_pack_start_defaults (GTK_BOX(hbox),
696                                 gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
697        l = gtk_label_new (NULL);
698        gtk_label_set_markup (GTK_LABEL(l), _( "<b>Leechers:</b>" ) );
699        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
700        l = p->leechers_lb = gtk_label_new (NULL);
701        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
702    gtk_box_pack_start_defaults (GTK_BOX(hbox),
703                                 gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
704        l = gtk_label_new (NULL);
705        gtk_label_set_markup (GTK_LABEL(l), _( "<b>Completed:</b>" ) );
706        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
707        l = p->completed_lb = gtk_label_new (NULL);
708        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
709    gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
710
711  ret = vbox;
712  p->gtor = gtor;
713  p->model = m;
714  p->store = GTK_TREE_STORE(m);
715  g_object_set_data_full (G_OBJECT(ret), "peer-data", p, g_free);
716  return ret;
717}
718
719/****
720*****  INFO TAB
721****/
722
723static void
724refresh_time_lb( GtkWidget * l, time_t t )
725{
726    const char * never = _( "Never" );
727    if( !t )
728        gtk_label_set_text( GTK_LABEL( l ), never );
729    else {
730        char * str = rfc822date( t );
731        gtk_label_set_text( GTK_LABEL( l ), str );
732        g_free( str );
733    }
734}
735
736static GtkWidget*
737info_page_new (tr_torrent * tor)
738{
739  int row = 0;
740  GtkWidget *t = hig_workarea_create ();
741  GtkWidget *l, *w, *fr;
742  char *pch;
743  char sizeStr[128];
744  char countStr[128];
745  char buf[256];
746  GtkTextBuffer * b;
747  const tr_info * info = tr_torrentInfo(tor);
748
749  hig_workarea_add_section_title (t, &row, _("Details"));
750
751    g_snprintf( countStr, sizeof( countStr ),
752                ngettext( "%d Piece", "%d Pieces", info->pieceCount ),
753                info->pieceCount );
754    tr_strlsize( sizeStr, info->pieceSize, sizeof(sizeStr) );
755    g_snprintf( buf, sizeof( buf ),
756                /* %1$s is number of pieces;
757                   %2$s is how big each piece is */
758                _( "%1$s @ %2$s" ),
759              countStr, sizeStr );
760
761    l = gtk_label_new (buf);
762    hig_workarea_add_row (t, &row, _("Pieces:"), l, NULL);
763
764    l = g_object_new( GTK_TYPE_LABEL, "label", info->hashString, "selectable", TRUE,
765                                      "ellipsize", PANGO_ELLIPSIZE_END, NULL );
766    hig_workarea_add_row (t, &row, _("Hash:"), l, NULL);
767
768    pch = (info->isPrivate )
769      ? _("Private to this tracker -- PEX disabled")
770      : _("Public torrent");
771    l = gtk_label_new (pch);
772    hig_workarea_add_row (t, &row, _("Privacy:"), l, NULL);
773
774    b = gtk_text_buffer_new (NULL);
775    if( info->comment )
776        gtk_text_buffer_set_text (b, info->comment, -1);
777    w = gtk_text_view_new_with_buffer (b);
778    gtk_widget_set_size_request (w, 0u, 100u);
779    gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW(w), GTK_WRAP_WORD); 
780    gtk_text_view_set_editable (GTK_TEXT_VIEW(w), FALSE);
781    fr = gtk_frame_new (NULL);
782    gtk_frame_set_shadow_type (GTK_FRAME(fr), GTK_SHADOW_IN);
783    gtk_container_add (GTK_CONTAINER(fr), w);
784    w = hig_workarea_add_row (t, &row, _("Comment:"), fr, NULL);
785    gtk_misc_set_alignment (GTK_MISC(w), 0.0f, 0.0f);
786
787  hig_workarea_add_section_divider (t, &row);
788  hig_workarea_add_section_title (t, &row, _("Origins"));
789 
790    l = gtk_label_new (*info->creator ? info->creator : _("Unknown"));
791    hig_workarea_add_row (t, &row, _("Creator:"), l, NULL);
792
793    l = gtk_label_new( NULL );
794    refresh_time_lb( l, info->dateCreated );
795    hig_workarea_add_row (t, &row, _("Date:"), l, NULL); 
796
797  hig_workarea_add_section_divider (t, &row);
798  hig_workarea_add_section_title (t, &row, _("Location"));
799
800    l = g_object_new( GTK_TYPE_LABEL, "label", tr_torrentGetDownloadDir( tor ), "selectable", TRUE,
801                                      "ellipsize", PANGO_ELLIPSIZE_END, NULL );
802    hig_workarea_add_row (t, &row, _( "Destination folder:" ), l, NULL); 
803
804    l = g_object_new( GTK_TYPE_LABEL, "label", info->torrent, "selectable", TRUE,
805                                      "ellipsize", PANGO_ELLIPSIZE_END, NULL );
806    hig_workarea_add_row (t, &row, _( "Torrent file:" ), l, NULL); 
807
808  hig_workarea_finish (t, &row);
809  return t;
810}
811
812/****
813*****  ACTIVITY TAB
814****/
815
816typedef struct
817{
818  GtkWidget * state_lb;
819  GtkWidget * progress_lb;
820  GtkWidget * have_lb;
821  GtkWidget * dl_lb;
822  GtkWidget * ul_lb;
823  GtkWidget * failed_lb;
824  GtkWidget * ratio_lb;
825  GtkWidget * err_lb;
826  GtkWidget * swarm_lb;
827  GtkWidget * date_added_lb;
828  GtkWidget * last_activity_lb;
829  GtkWidget * availability_da;
830  TrTorrent * gtor;
831}
832Activity;
833
834static void
835refresh_activity (GtkWidget * top)
836{
837  Activity * a = g_object_get_data (G_OBJECT(top), "activity-data");
838  char *pch;
839  char sizeStr[64];
840  char sizeStr2[64];
841  char buf[128];
842  const tr_stat * stat = tr_torrent_stat( a->gtor );
843  const double complete = stat->percentComplete * 100.0;
844  const double done = stat->percentDone * 100.0;
845
846  pch = tr_torrent_status_str( a->gtor );
847  gtk_label_set_text (GTK_LABEL(a->state_lb), pch);
848  g_free (pch);
849
850  if( (int)complete == (int)done )
851    pch = g_strdup_printf( _( "%.1f%%" ), complete );
852  else
853    /* %1$.1f is percent of how much of what we want's been downloaded,
854       %2$.1f is percent of how much of the whole torrent we've downloaded */
855    pch = g_strdup_printf( _( "%1$.1f%% (%2$.1f%% selected)" ), complete, done );
856  gtk_label_set_text (GTK_LABEL(a->progress_lb), pch);
857  g_free (pch);
858
859  tr_strlsize( sizeStr,  stat->haveValid + stat->haveUnchecked, sizeof(sizeStr) );
860  tr_strlsize( sizeStr2, stat->haveValid,                       sizeof(sizeStr2) );
861  /* %1$s is total size of what we've saved to disk
862     %2$s is how much of it's passed the checksum test */
863  g_snprintf( buf, sizeof(buf), _("%1$s (%2$s verified)"), sizeStr, sizeStr2 );
864  gtk_label_set_text( GTK_LABEL( a->have_lb ), buf );
865
866  tr_strlsize( sizeStr, stat->downloadedEver, sizeof(sizeStr) );
867  gtk_label_set_text( GTK_LABEL(a->dl_lb), sizeStr );
868
869  tr_strlsize( sizeStr, stat->uploadedEver, sizeof(sizeStr) );
870  gtk_label_set_text( GTK_LABEL(a->ul_lb), sizeStr );
871
872  tr_strlsize( sizeStr, stat->corruptEver, sizeof( sizeStr ) );
873  gtk_label_set_text( GTK_LABEL( a->failed_lb ), sizeStr );
874
875  tr_strlratio( buf, stat->ratio, sizeof( buf ) );
876  gtk_label_set_text( GTK_LABEL( a->ratio_lb ), buf );
877
878  tr_strlspeed( buf, stat->swarmSpeed, sizeof(buf) );
879  gtk_label_set_text (GTK_LABEL(a->swarm_lb), buf );
880
881  gtk_label_set_text (GTK_LABEL(a->err_lb),
882                      *stat->errorString ? stat->errorString : _("None"));
883
884  refresh_time_lb( a->date_added_lb, stat->addedDate );
885
886  refresh_time_lb( a->last_activity_lb, stat->activityDate );
887
888#ifdef SHOW_PIECES
889  if (GDK_IS_DRAWABLE (a->availability_da->window))
890    refresh_pieces (a->availability_da, NULL, a->gtor);
891#endif
892}
893 
894
895static GtkWidget*
896activity_page_new (TrTorrent * gtor)
897{
898  Activity * a = g_new (Activity, 1);
899  int row = 0;
900  GtkWidget *t = hig_workarea_create ();
901  GtkWidget *l;
902
903  a->gtor = gtor;
904
905  hig_workarea_add_section_title (t, &row, _("Transfer"));
906
907    l = a->state_lb = gtk_label_new (NULL);
908    hig_workarea_add_row (t, &row, _("State:"), l, NULL);
909
910    l = a->progress_lb = gtk_label_new (NULL);
911    hig_workarea_add_row (t, &row, _("Progress:"), l, NULL);
912
913    l = a->have_lb = gtk_label_new (NULL);
914    /* "Have" refers to how much of the torrent we have */
915    hig_workarea_add_row (t, &row, _("Have:"), l, NULL);
916
917    l = a->dl_lb = gtk_label_new (NULL);
918    hig_workarea_add_row (t, &row, _("Downloaded:"), l, NULL);
919
920    l = a->ul_lb = gtk_label_new (NULL);
921    hig_workarea_add_row (t, &row, _("Uploaded:"), l, NULL);
922
923    /* how much downloaded data was corrupt */
924    l = a->failed_lb = gtk_label_new (NULL);
925    hig_workarea_add_row (t, &row, _("Failed DL:"), l, NULL);
926
927    l = a->ratio_lb = gtk_label_new (NULL);
928    hig_workarea_add_row (t, &row, _("Ratio:"), l, NULL);
929
930    l = a->swarm_lb = gtk_label_new (NULL);
931    hig_workarea_add_row (t, &row, _("Swarm rate:"), l, NULL);
932
933    l = a->err_lb = gtk_label_new (NULL);
934    hig_workarea_add_row (t, &row, _("Error:"), l, NULL);
935
936#ifdef SHOW_PIECES
937  hig_workarea_add_section_divider (t, &row);
938  hig_workarea_add_section_title (t, &row, _("Completion"));
939
940    w = a->availability_da = gtk_drawing_area_new ();
941    gtk_widget_set_size_request (w, 0u, 100u);
942    g_object_set_data (G_OBJECT(w), "draw-mode", GINT_TO_POINTER(DRAW_PROG));
943    g_signal_connect (w, "expose-event", G_CALLBACK(refresh_pieces), gtor);
944    hig_workarea_add_wide_control( t, &row, w );
945#endif
946
947  hig_workarea_add_section_divider (t, &row);
948  hig_workarea_add_section_title (t, &row, _("Dates"));
949
950    l = a->date_added_lb = gtk_label_new (NULL);
951    hig_workarea_add_row (t, &row, _("Started at:"), l, NULL);
952
953    l = a->last_activity_lb = gtk_label_new (NULL);
954    hig_workarea_add_row (t, &row, _("Last activity at:"), l, NULL);
955
956  hig_workarea_add_section_divider (t, &row);
957  hig_workarea_finish (t, &row);
958  g_object_set_data_full (G_OBJECT(t), "activity-data", a, g_free);
959  return t;
960}
961
962
963/****
964*****  OPTIONS
965****/
966
967static void
968speed_toggled_cb( GtkToggleButton * tb, gpointer gtor, int up_or_down )
969{
970  tr_torrent * tor = tr_torrent_handle (gtor);
971  gboolean b = gtk_toggle_button_get_active(tb);
972  tr_torrentSetSpeedMode( tor, up_or_down, b ? TR_SPEEDLIMIT_SINGLE
973                                             : TR_SPEEDLIMIT_GLOBAL );
974}
975static void
976ul_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
977{
978  speed_toggled_cb( tb, gtor, TR_UP );
979}
980static void
981dl_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
982{
983  speed_toggled_cb( tb, gtor, TR_DOWN );
984}
985
986
987static void
988sensitize_from_check_cb (GtkToggleButton *toggle, gpointer w)
989{
990  gtk_widget_set_sensitive (GTK_WIDGET(w),
991                            gtk_toggle_button_get_active(toggle));
992}
993
994static void
995setSpeedLimit( GtkSpinButton* spin, gpointer gtor, int up_or_down )
996{
997  tr_torrent * tor = tr_torrent_handle (gtor);
998  int kb_sec = gtk_spin_button_get_value_as_int (spin);
999  tr_torrentSetSpeedLimit( tor, up_or_down, kb_sec );
1000}
1001static void
1002ul_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
1003{
1004  setSpeedLimit( spin, gtor, TR_UP );
1005}
1006static void
1007dl_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
1008{
1009  setSpeedLimit( spin, gtor, TR_DOWN );
1010}
1011
1012static void
1013max_peers_spun_cb( GtkSpinButton * spin, gpointer gtor )
1014{
1015  const uint16_t n = gtk_spin_button_get_value( spin );
1016  tr_torrentSetPeerLimit( tr_torrent_handle( gtor ), n );
1017}
1018
1019static GtkWidget*
1020options_page_new ( TrTorrent * gtor )
1021{
1022  uint16_t maxConnectedPeers;
1023  int i, row;
1024  gboolean b;
1025  GtkAdjustment *a;
1026  GtkWidget *t, *w, *tb;
1027  tr_torrent * tor = tr_torrent_handle (gtor);
1028
1029  row = 0;
1030  t = hig_workarea_create ();
1031  hig_workarea_add_section_title (t, &row, _("Bandwidth") );
1032
1033    tb = gtk_check_button_new_with_mnemonic (_("Limit _download speed (KB/s):"));
1034    b = tr_torrentGetSpeedMode(tor,TR_DOWN) == TR_SPEEDLIMIT_SINGLE;
1035    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), b );
1036    g_signal_connect (tb, "toggled", G_CALLBACK(dl_speed_toggled_cb), gtor);
1037
1038    i = tr_torrentGetSpeedLimit( tor, TR_DOWN );
1039    a = (GtkAdjustment*) gtk_adjustment_new (i, 0.0, G_MAXDOUBLE, 1, 1, 1);
1040    w = gtk_spin_button_new (a, 1, 0);
1041    g_signal_connect (w, "value-changed", G_CALLBACK(dl_speed_spun_cb), gtor);
1042    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1043    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1044    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1045
1046    tb = gtk_check_button_new_with_mnemonic (_("Limit _upload speed (KB/s):"));
1047    b = tr_torrentGetSpeedMode(tor,TR_UP) == TR_SPEEDLIMIT_SINGLE;
1048    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), b );
1049    g_signal_connect (tb, "toggled", G_CALLBACK(ul_speed_toggled_cb), gtor);
1050
1051    i = tr_torrentGetSpeedLimit( tor, TR_UP );
1052    a = (GtkAdjustment*) gtk_adjustment_new (i, 0.0, G_MAXDOUBLE, 1, 1, 1);
1053    w = gtk_spin_button_new (a, 1, 0);
1054    g_signal_connect (w, "value-changed", G_CALLBACK(ul_speed_spun_cb), gtor);
1055    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1056    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1057    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1058
1059  hig_workarea_add_section_divider (t, &row);
1060  hig_workarea_add_section_title (t, &row, _("Peer Connections"));
1061
1062    maxConnectedPeers = tr_torrentGetPeerLimit( tor );
1063    w = gtk_spin_button_new_with_range( 1, 3000, 5 );
1064    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), maxConnectedPeers );
1065    hig_workarea_add_row( t, &row, _( "_Maximum peers:" ), w, w );
1066    g_signal_connect( w, "value-changed", G_CALLBACK( max_peers_spun_cb ), gtor );
1067
1068  hig_workarea_finish (t, &row);
1069  return t;
1070}
1071
1072static void
1073refresh_options (GtkWidget * top UNUSED)
1074{
1075}
1076
1077/****
1078*****  TRACKER
1079****/
1080
1081#define TRACKER_PAGE "tracker-page"
1082
1083struct tracker_page
1084{
1085    TrTorrent * gtor;
1086
1087    GtkTreeView * view;
1088    GtkListStore * store;
1089    GtkTreeSelection * sel;
1090
1091    GtkWidget * add_button;
1092    GtkWidget * remove_button;
1093    GtkWidget * save_button;
1094    GtkWidget * revert_button;
1095
1096    GtkWidget * last_scrape_time_lb;
1097    GtkWidget * last_scrape_response_lb;
1098    GtkWidget * next_scrape_countdown_lb;
1099
1100    GtkWidget * last_announce_time_lb;
1101    GtkWidget * last_announce_response_lb;
1102    GtkWidget * next_announce_countdown_lb;
1103    GtkWidget * manual_announce_countdown_lb;
1104};
1105
1106GtkWidget*
1107tracker_page_new( TrTorrent * gtor )
1108{
1109    GtkWidget * t;
1110    GtkWidget * l;
1111    GtkWidget * w;
1112    int row = 0;
1113    const char * s;
1114    struct tracker_page * page = g_new0( struct tracker_page, 1 );
1115    const tr_info * info = tr_torrent_info (gtor);
1116
1117    page->gtor = gtor;
1118
1119    t = hig_workarea_create( );
1120    hig_workarea_add_section_title( t, &row, _( "Trackers" ) );
1121
1122        w = tracker_list_new( gtor );
1123        hig_workarea_add_wide_control( t, &row, w );
1124
1125    hig_workarea_add_section_divider( t, &row );
1126    hig_workarea_add_section_title( t, &row, _( "Scrape" ) );
1127
1128        s = _( "Last scrape at:" );
1129        l = gtk_label_new( NULL );
1130        page->last_scrape_time_lb = l;
1131        hig_workarea_add_row( t, &row, s, l, NULL );
1132
1133        s = _( "Tracker responded:");
1134        l = gtk_label_new( NULL );
1135        page->last_scrape_response_lb = l;
1136        hig_workarea_add_row( t, &row, s, l, NULL );
1137
1138        s = _( "Next scrape in:" );
1139        l = gtk_label_new( NULL );
1140        page->next_scrape_countdown_lb = l;
1141        hig_workarea_add_row( t, &row, s, l, NULL );
1142
1143    hig_workarea_add_section_divider( t, &row );
1144    hig_workarea_add_section_title( t, &row, _( "Announce" ) );
1145
1146        l = gtk_label_new( info->trackers[0].announce );
1147        gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
1148        hig_workarea_add_row (t, &row, _( "Tracker:" ), l, NULL);
1149
1150        s = _( "Last announce at:" );
1151        l = gtk_label_new( NULL );
1152        page->last_announce_time_lb = l;
1153        hig_workarea_add_row( t, &row, s, l, NULL );
1154
1155        s = _( "Tracker responded:");
1156        l = gtk_label_new( NULL );
1157        page->last_announce_response_lb = l;
1158        hig_workarea_add_row( t, &row, s, l, NULL );
1159
1160        s = _( "Next announce in:" );
1161        l = gtk_label_new( NULL );
1162        page->next_announce_countdown_lb = l;
1163        hig_workarea_add_row( t, &row, s, l, NULL );
1164
1165        /* how long until the tracker will honor user
1166         * pressing the "ask for more peers" button */
1167        s = _( "Manual announce allowed in:" );
1168        l = gtk_label_new( NULL );
1169        page->manual_announce_countdown_lb = l;
1170        hig_workarea_add_row( t, &row, s, l, NULL );
1171
1172    hig_workarea_finish( t, &row );
1173    g_object_set_data_full( G_OBJECT( t ), TRACKER_PAGE, page, g_free );
1174    return t;
1175}
1176
1177static void
1178refresh_countdown_lb( GtkWidget * l, time_t t )
1179{
1180    const time_t now = time( NULL );
1181
1182    if( !t || ( t < now ) )
1183        gtk_label_set_text( GTK_LABEL( l ), _( "Never" ) );
1184    else {
1185        char buf[1024];
1186        const int seconds = t - now;
1187        tr_strltime( buf, seconds, sizeof( buf ) );
1188        gtk_label_set_text( GTK_LABEL( l ), buf );
1189    }
1190}
1191
1192static void
1193refresh_tracker( GtkWidget * w )
1194{
1195    GtkWidget * l;
1196    time_t t;
1197    struct tracker_page * page = g_object_get_data( G_OBJECT( w ), TRACKER_PAGE );
1198    const tr_stat * torStat = tr_torrent_stat( page->gtor );
1199
1200    l = page->last_scrape_time_lb;
1201    t = torStat->lastScrapeTime;
1202    refresh_time_lb( l, t );
1203
1204    l = page->last_scrape_response_lb;
1205    gtk_label_set_text( GTK_LABEL( l ), torStat->scrapeResponse );
1206
1207    l = page->next_scrape_countdown_lb;
1208    t = torStat->nextScrapeTime;
1209    refresh_countdown_lb( l, t );
1210
1211    l = page->last_announce_time_lb;
1212    t = torStat->lastAnnounceTime;
1213    refresh_time_lb( l, t );
1214
1215    l = page->last_announce_response_lb;
1216    gtk_label_set_text( GTK_LABEL( l ), torStat->announceResponse );
1217
1218    l = page->next_announce_countdown_lb;
1219    t = torStat->nextAnnounceTime;
1220    refresh_countdown_lb( l, t );
1221
1222    l = page->manual_announce_countdown_lb;
1223    t = torStat->manualAnnounceTime;
1224    refresh_countdown_lb( l, t );
1225}
1226
1227/****
1228*****  DIALOG
1229****/
1230
1231static void
1232torrent_destroyed (gpointer dialog, GObject * dead_torrent UNUSED)
1233{
1234  gtk_widget_destroy (GTK_WIDGET(dialog));
1235}
1236
1237static void
1238remove_tag (gpointer tag)
1239{
1240  g_source_remove (GPOINTER_TO_UINT(tag)); /* stop the periodic refresh */
1241}
1242
1243static void
1244response_cb (GtkDialog *dialog, int response UNUSED, gpointer gtor)
1245{
1246  g_object_weak_unref (G_OBJECT(gtor), torrent_destroyed, dialog);
1247  gtk_widget_destroy (GTK_WIDGET(dialog));
1248}
1249
1250static gboolean
1251periodic_refresh (gpointer data)
1252{
1253  refresh_tracker   (g_object_get_data (G_OBJECT(data), "tracker-top"));
1254  refresh_peers     (g_object_get_data (G_OBJECT(data), "peers-top"));
1255  refresh_activity  (g_object_get_data (G_OBJECT(data), "activity-top"));
1256  refresh_options   (g_object_get_data (G_OBJECT(data), "options-top"));
1257  return TRUE;
1258}
1259
1260GtkWidget*
1261torrent_inspector_new ( GtkWindow * parent, TrTorrent * gtor )
1262{
1263  guint tag;
1264  GtkWidget *d, *n, *w;
1265  tr_torrent * tor = tr_torrent_handle (gtor);
1266  char sizeStr[64];
1267  char title[512];
1268  const tr_info * info = tr_torrent_info (gtor);
1269
1270  /* create the dialog */
1271  tr_strlsize( sizeStr, info->totalSize, sizeof(sizeStr) );
1272  /* %1$s is torrent name
1273     %2$s its file size */
1274  g_snprintf( title, sizeof(title), _( "Details for %1$s (%2$s)" ), info->name, sizeStr );
1275  d = gtk_dialog_new_with_buttons (title, parent, 0,
1276                                   GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1277                                   NULL);
1278  gtk_window_set_role (GTK_WINDOW(d), "tr-info" );
1279  g_signal_connect (d, "response", G_CALLBACK (response_cb), gtor);
1280  gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
1281  gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1282  g_object_weak_ref (G_OBJECT(gtor), torrent_destroyed, d);
1283 
1284
1285  /* add the notebook */
1286  n = gtk_notebook_new ();
1287  gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1288
1289  w = activity_page_new (gtor);
1290  g_object_set_data (G_OBJECT(d), "activity-top", w);
1291  gtk_notebook_append_page (GTK_NOTEBOOK(n), w, 
1292                            gtk_label_new (_("Activity")));
1293
1294  w = peer_page_new (gtor);
1295  g_object_set_data (G_OBJECT(d), "peers-top", w);
1296  gtk_notebook_append_page (GTK_NOTEBOOK(n),  w,
1297                            gtk_label_new (_("Peers")));
1298
1299  w = tracker_page_new( gtor );
1300  g_object_set_data( G_OBJECT( d ), "tracker-top", w );
1301  gtk_notebook_append_page( GTK_NOTEBOOK( n ), w,
1302                            gtk_label_new( _( "Tracker" ) ) );
1303
1304  gtk_notebook_append_page (GTK_NOTEBOOK(n),
1305                            info_page_new (tor),
1306                            gtk_label_new (_("Information")));
1307
1308  w = file_list_new( gtor );
1309  gtk_container_set_border_width( GTK_CONTAINER( w ), GUI_PAD_BIG );
1310  g_object_set_data (G_OBJECT(d), "files-top", w);
1311  gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
1312                            gtk_label_new (_("Files")));
1313
1314  w = options_page_new (gtor);
1315  g_object_set_data (G_OBJECT(d), "options-top", w);
1316  gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
1317                            gtk_label_new (_("Options")));
1318
1319  gtk_box_pack_start_defaults (GTK_BOX(GTK_DIALOG(d)->vbox), n);
1320
1321  tag = g_timeout_add (UPDATE_INTERVAL_MSEC, periodic_refresh, d);
1322  g_object_set_data_full (G_OBJECT(d), "tag",
1323                          GUINT_TO_POINTER(tag), remove_tag);
1324
1325  /* return the results */
1326  periodic_refresh (d);
1327  gtk_widget_show_all (GTK_DIALOG(d)->vbox);
1328  return d;
1329}
Note: See TracBrowser for help on using the repository browser.