source: trunk/gtk/torrent-inspector.c @ 4327

Last change on this file since 4327 was 4327, checked in by charles, 15 years ago

(gtk) narrow the torrent inspector a bit.

  • Property svn:keywords set to Date Rev Author Id
File size: 58.1 KB
Line 
1/******************************************************************************
2 * $Id: torrent-inspector.c 4327 2007-12-25 08:03:35Z charles $
3 *
4 * Copyright (c) 2005-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <stddef.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <glib/gi18n.h>
29#include <gtk/gtk.h>
30
31#include <libtransmission/transmission.h>
32
33#include "actions.h"
34#include "tr_torrent.h"
35#include "hig.h"
36#include "torrent-inspector.h"
37#include "util.h"
38
39#define UPDATE_INTERVAL_MSEC 2000
40
41/****
42*****  PIECES VIEW
43****/
44
45static int
46getGridSize (int pieceCount, int * n_rows, int * n_cols)
47{
48  const int MAX_ACROSS = 16;
49  if (pieceCount >= (MAX_ACROSS * MAX_ACROSS)) {
50    *n_rows = *n_cols = MAX_ACROSS;
51    return MAX_ACROSS * MAX_ACROSS;
52  }
53  else {
54    int i;
55    for (i=0; (i*i) < pieceCount; ++i);
56    *n_rows = *n_cols = i;
57    return pieceCount;
58  }
59}
60
61#define TO16(a) ((guint16)((a<<8)|(a)))
62#define RGB_2_GDK(R,G,B) { 0, TO16(R), TO16(G), TO16(B) }
63
64enum { DRAW_AVAIL, DRAW_PROG };
65
66static void
67release_gobject_array (gpointer data)
68{
69  int i;
70  GObject **objects = (GObject**) data;
71  for (i=0; objects[i]!=NULL; ++i)
72    g_object_unref (G_OBJECT(objects[i]));
73  g_free (objects);
74}
75
76static gboolean
77refresh_pieces (GtkWidget * da, GdkEventExpose * event UNUSED, gpointer gtor)
78{
79  tr_torrent * tor = tr_torrent_handle( TR_TORRENT(gtor) );
80  const tr_info * info = tr_torrent_info( TR_TORRENT(gtor) );
81  int mode = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(da), "draw-mode"));
82
83  GdkColormap * colormap = gtk_widget_get_colormap (da);
84  const int widget_w = da->allocation.width;
85  const int widget_h = da->allocation.height;
86  int n_rows, n_cols;
87  const int n_cells = getGridSize (info->pieceCount,  &n_rows, &n_cols);
88  const GdkRectangle grid_bounds = { 0, 0, widget_w, widget_h };
89  const double piece_w = grid_bounds.width / (double)n_cols;
90  const double piece_h = grid_bounds.height / (double)n_rows;
91  const int piece_w_int = (int) (piece_w + 1); /* pad for roundoff */
92  const int piece_h_int = (int) (piece_h + 1); /* pad for roundoff */
93  guint8 * prev_color = NULL;
94  gboolean first_time = FALSE;
95
96  int i, x, y;
97  int8_t * pieces = NULL;
98  float * completeness = NULL;
99
100  /**
101  ***  Get the Graphics Contexts...
102  **/
103
104  enum { ALL, LOTS, SOME, FEW, NONE,
105         BLACK, GRAY, BLINK,
106         N_COLORS };
107  GdkGC **gcs = (GdkGC**) g_object_get_data (G_OBJECT(da), "graphics-contexts");
108  if (gcs == NULL)
109  {
110    const GdkColor colors [N_COLORS] = {
111      RGB_2_GDK ( 114, 159, 207 ), /* all */
112      RGB_2_GDK (  52, 101, 164 ), /* lots */
113      RGB_2_GDK (  32,  74, 135 ), /* some */
114      RGB_2_GDK (  85,  87,  83 ), /* few */
115      RGB_2_GDK ( 238, 238, 236 ), /* none - tango aluminum highlight */
116      RGB_2_GDK (  46,  52,  54 ), /* black - tango slate shadow */
117      RGB_2_GDK ( 186, 189, 182 ), /* gray - tango aluminum shadow */
118      RGB_2_GDK ( 252, 233,  79 ), /* blink - tango butter highlight */
119    };
120
121    gcs = g_new (GdkGC*, N_COLORS+1);
122
123    for (i=0; i<N_COLORS; ++i) {
124      gcs[i] = gdk_gc_new (da->window);
125      gdk_gc_set_colormap (gcs[i], colormap);
126      gdk_gc_set_rgb_fg_color (gcs[i], &colors[i]);
127      gdk_gc_set_rgb_bg_color (gcs[i], &colors[i]);
128    };
129
130    gcs[N_COLORS] = NULL; /* a sentinel in the release function */
131    g_object_set_data_full (G_OBJECT(da), "graphics-contexts",
132                            gcs, release_gobject_array);
133  }
134
135  /**
136  ***  Get the cells' previous colors...
137  ***  (this is used for blinking when the color changes)
138  **/
139
140  prev_color = (guint8*) g_object_get_data (G_OBJECT(da), "prev-color");
141  if (prev_color == NULL)
142  {
143    first_time = TRUE;
144    prev_color = g_new0 (guint8, n_cells);
145    g_object_set_data_full (G_OBJECT(da), "prev-color", prev_color, g_free);
146  }
147
148  /**
149  ***  Get the piece data values...
150  **/
151
152  switch (mode) {
153    case DRAW_AVAIL:
154      pieces = g_new (int8_t, n_cells);
155      tr_torrentAvailability ( tor, pieces, n_cells );
156      break;
157    case DRAW_PROG:
158      completeness = g_new (float, n_cells);
159      tr_torrentAmountFinished ( tor, completeness, n_cells );
160      break;
161    default: g_error("no mode defined!");
162  }
163
164  /**
165  ***  Draw...
166  **/
167
168  i = 0; 
169  for (y=0; y<n_rows; ++y) {
170    for (x=0; x<n_cols; ++x) {
171      const int draw_x = grid_bounds.x + (int)(x * piece_w);
172      const int draw_y = grid_bounds.y + (int)(y * piece_h);
173      int color = BLACK;
174      int border = BLACK;
175
176      if (i < n_cells)
177      {
178        border = GRAY;
179
180        if (mode == DRAW_AVAIL) {
181          const int8_t val = pieces[i];
182               if (val <  0) color = ALL;
183          else if (val == 0) color = NONE;
184          else if (val <= 4) color = FEW;
185          else if (val <= 8) color = SOME;
186          else               color = LOTS;
187        } else { /* completeness */
188          const float val = completeness[i];
189               if (val >= 1.00) color = ALL;
190          else if (val >= 0.66) color = LOTS;
191          else if (val >= 0.33) color = SOME;
192          else if (val >= 0.01) color = FEW;
193          else                  color = NONE;
194        }
195
196        /* draw a "blink" for one interval when a piece changes */
197        if (first_time)
198          prev_color[i] = color;
199        else if (color != prev_color[i]) {
200          prev_color[i] = color;
201          color = border = BLINK;
202        }
203      }
204
205      gdk_draw_rectangle (da->window, gcs[color], TRUE,
206                          draw_x, draw_y,
207                          piece_w_int, piece_h_int);
208
209      if (i < n_cells)
210        gdk_draw_rectangle (da->window, gcs[border], FALSE,
211                            draw_x, draw_y,
212                            piece_w_int, piece_h_int);
213
214      ++i;
215    }
216  }
217
218  gdk_draw_rectangle (da->window, gcs[GRAY], FALSE,
219                      grid_bounds.x, grid_bounds.y, 
220                      grid_bounds.width-1, grid_bounds.height-1);
221
222  g_free (pieces);
223  g_free (completeness);
224  return FALSE;
225}
226
227/****
228*****  PEERS TAB
229****/
230
231enum
232{
233  PEER_COL_ADDRESS,
234  PEER_COL_PORT,
235  PEER_COL_CLIENT,
236  PEER_COL_PROGRESS,
237  PEER_COL_IS_ENCRYPTED,
238  PEER_COL_IS_DOWNLOADING,
239  PEER_COL_DOWNLOAD_RATE,
240  PEER_COL_IS_UPLOADING,
241  PEER_COL_UPLOAD_RATE,
242  PEER_COL_STATUS,
243  N_PEER_COLS
244};
245
246static const char* peer_column_names[N_PEER_COLS] =
247{
248  N_("Address"),
249  N_("Port"),
250  N_("Client"),
251  N_("Progress"),
252  " ",
253  N_("Downloading"),
254  N_("DL Rate"),
255  N_("Uploading"),
256  N_("UL Rate"),
257  N_("Status")
258};
259
260static int compare_peers (const void * a, const void * b)
261{
262  const tr_peer_stat * pa = a;
263  const tr_peer_stat * pb = b;
264  return strcmp (pa->addr, pb->addr);
265}
266static int compare_addr_to_peer (const void * a, const void * b)
267{
268  const char * addr = (const char *) a;
269  const tr_peer_stat * peer = b;
270  return strcmp (addr, peer->addr);
271}
272
273static void
274peer_row_set (GtkTreeStore        * store,
275              GtkTreeIter         * iter,
276              const tr_peer_stat  * peer)
277{
278  const char * client = peer->client;
279
280  if (!client || !strcmp(client,"Unknown Client"))
281    client = " ";
282
283  gtk_tree_store_set (store, iter,
284                      PEER_COL_ADDRESS, peer->addr,
285                      PEER_COL_PORT, peer->port,
286                      PEER_COL_CLIENT, client,
287                      PEER_COL_IS_ENCRYPTED, peer->isEncrypted,
288                      PEER_COL_PROGRESS, (int)(100.0*peer->progress),
289                      PEER_COL_IS_DOWNLOADING, peer->isDownloading,
290                      PEER_COL_DOWNLOAD_RATE, peer->downloadFromRate,
291                      PEER_COL_IS_UPLOADING, peer->isUploading,
292                      PEER_COL_UPLOAD_RATE, peer->uploadToRate,
293                      PEER_COL_STATUS, peer->status,
294                      -1);
295}
296
297static void
298append_peers_to_model (GtkTreeStore        * store,
299                       const tr_peer_stat  * peers,
300                       int                   n_peers)
301{
302  int i;
303  for (i=0; i<n_peers; ++i) {
304    GtkTreeIter iter;
305    gtk_tree_store_append (store, &iter, NULL);
306    peer_row_set (store, &iter, &peers[i]);
307  }
308}
309
310static GtkTreeModel*
311peer_model_new (tr_torrent * tor)
312{
313  GtkTreeStore * m = gtk_tree_store_new (N_PEER_COLS,
314                                         G_TYPE_STRING,  /* addr */
315                                         G_TYPE_INT,     /* port */
316                                         G_TYPE_STRING,  /* client */
317                                         G_TYPE_INT,     /* progress [0..100] */
318                                         G_TYPE_BOOLEAN, /* isEncrypted */
319                                         G_TYPE_BOOLEAN, /* isDownloading */
320                                         G_TYPE_FLOAT,   /* downloadFromRate */
321                                         G_TYPE_BOOLEAN, /* isUploading */
322                                         G_TYPE_FLOAT,   /* uploadToRate */
323                                         G_TYPE_INT );   /* tr_peer_status */
324
325  int n_peers = 0;
326  tr_peer_stat * peers = tr_torrentPeers (tor, &n_peers);
327  qsort (peers, n_peers, sizeof(tr_peer_stat), compare_peers);
328  append_peers_to_model (m, peers, n_peers);
329  tr_torrentPeersFree( peers, 0 );
330  return GTK_TREE_MODEL (m);
331}
332
333static void
334render_encrypted (GtkTreeViewColumn  * column UNUSED,
335                  GtkCellRenderer    * renderer,
336                  GtkTreeModel       * tree_model,
337                  GtkTreeIter        * iter,
338                  gpointer             data UNUSED)
339{
340  gboolean is_encrypted = FALSE;
341  gtk_tree_model_get (tree_model, iter, PEER_COL_IS_ENCRYPTED, &is_encrypted,
342                                        -1);
343  g_object_set (renderer, "xalign", (gfloat)0.0,
344                          "yalign", (gfloat)0.5,
345                          "stock-id", (is_encrypted ? "transmission-lock" : NULL),
346                          NULL);
347}
348
349static void
350render_status( GtkTreeViewColumn  * column UNUSED,
351               GtkCellRenderer    * renderer,
352               GtkTreeModel       * tree_model,
353               GtkTreeIter        * iter,
354               gpointer             data UNUSED )
355{
356    GString * gstr = g_string_new( NULL );
357    int status;
358    gtk_tree_model_get( tree_model, iter, PEER_COL_STATUS, &status, -1 );
359
360    if( status & TR_PEER_STATUS_HANDSHAKE )
361    {
362        g_string_append( gstr, _("Handshaking") );
363    }
364    else
365    {
366        if( status & TR_PEER_STATUS_CLIENT_IS_SENDING )
367            g_string_append( gstr, _("Uploading to peer") );
368        else if( status & TR_PEER_STATUS_PEER_IS_INTERESTED )
369            g_string_append( gstr, _("Peer wants our data") );
370        else if( status & TR_PEER_STATUS_PEER_IS_CHOKED )
371            g_string_append( gstr, _("Refusing to send data to peer") );
372
373        g_string_append( gstr, " - " );
374
375        if( status & TR_PEER_STATUS_PEER_IS_SENDING )
376            g_string_append( gstr, _("Downloading from peer") );
377        else if( status & TR_PEER_STATUS_CLIENT_SENT_REQUEST )
378            g_string_append( gstr, _("Requesting data from peer") );
379        else if( status & TR_PEER_STATUS_CLIENT_IS_INTERESTED )
380            g_string_append( gstr, _("Waiting to request data from peer") );
381        else if( status & TR_PEER_STATUS_CLIENT_IS_CHOKED )
382            g_string_append( gstr, _("Peer will not send us data") );
383    }
384
385    g_object_set( renderer, "text", gstr->str, NULL );
386    g_string_free( gstr, TRUE );
387}
388
389static void
390render_ul_rate (GtkTreeViewColumn  * column UNUSED,
391                GtkCellRenderer    * renderer,
392                GtkTreeModel       * tree_model,
393                GtkTreeIter        * iter,
394                gpointer             data UNUSED)
395{
396  float rate = 0.0;
397  gtk_tree_model_get (tree_model, iter, PEER_COL_UPLOAD_RATE, &rate, -1);
398  if( rate < 0.01 )
399    g_object_set (renderer, "text", "", NULL);
400  else {
401    char speedStr[64];
402    tr_strlspeed( speedStr, rate, sizeof(speedStr) );
403    g_object_set( renderer, "text", speedStr, NULL );
404  }
405}
406
407static void
408render_dl_rate (GtkTreeViewColumn  * column UNUSED,
409                GtkCellRenderer    * renderer,
410                GtkTreeModel       * tree_model,
411                GtkTreeIter        * iter,
412                gpointer             data UNUSED)
413{
414  float rate = 0.0;
415  gtk_tree_model_get (tree_model, iter, PEER_COL_DOWNLOAD_RATE, &rate, -1);
416  if( rate < 0.01 )
417    g_object_set (renderer, "text", "", NULL);
418  else {
419    char speedStr[64];
420    tr_strlspeed( speedStr, rate, sizeof(speedStr) );
421    g_object_set( renderer, "text", speedStr, NULL );
422  }
423}
424
425static void
426render_client (GtkTreeViewColumn   * column UNUSED,
427               GtkCellRenderer     * renderer,
428               GtkTreeModel        * tree_model,
429               GtkTreeIter         * iter,
430               gpointer              data UNUSED)
431{
432  char * client = NULL;
433  gtk_tree_model_get (tree_model, iter, PEER_COL_CLIENT, &client,
434                                        -1);
435  g_object_set (renderer, "text", (client ? client : ""), NULL);
436  g_free (client);
437}
438
439typedef struct
440{
441  TrTorrent * gtor;
442  GtkTreeModel * model; /* same object as store, but recast */
443  GtkTreeStore * store; /* same object as model, but recast */
444  GtkWidget * completeness;
445  GtkWidget * seeders_lb;
446  GtkWidget * leechers_lb;
447  GtkWidget * completed_lb;
448}
449PeerData;
450
451static void
452fmtpeercount (GtkWidget * l, int count)
453{
454  if( 0 > count ) {
455    gtk_label_set_text( GTK_LABEL(l), "?" );
456  } else {
457    char str[16];
458    g_snprintf( str, sizeof str, "%i", count );
459    gtk_label_set_text( GTK_LABEL(l), str );
460  }
461}
462
463static void
464refresh_peers (GtkWidget * top)
465{
466  int i;
467  int n_peers;
468  GtkTreeIter iter;
469  PeerData * p = (PeerData*) g_object_get_data (G_OBJECT(top), "peer-data");
470  tr_torrent * tor = tr_torrent_handle ( p->gtor );
471  GtkTreeModel * model = p->model;
472  GtkTreeStore * store = p->store;
473  tr_peer_stat * peers;
474  const tr_stat * stat = tr_torrent_stat( p->gtor );
475
476  /**
477  ***  merge the peer diffs into the tree model.
478  ***
479  ***  this is more complicated than creating a new model,
480  ***  but is also (a) more efficient and (b) doesn't undo
481  ***  the view's visible area and sorting on every refresh.
482  **/
483
484  n_peers = 0;
485  peers = tr_torrentPeers (tor, &n_peers);
486  qsort (peers, n_peers, sizeof(tr_peer_stat), compare_peers);
487
488  i = 0;
489  if (gtk_tree_model_get_iter_first (model, &iter)) do
490  {
491    char * addr = NULL;
492    tr_peer_stat * peer = NULL;
493    gtk_tree_model_get (model, &iter, PEER_COL_ADDRESS, &addr, -1);
494    peer = bsearch (addr, peers, n_peers, sizeof(tr_peer_stat),
495                    compare_addr_to_peer);
496    g_free (addr);
497
498    if (peer) /* update a pre-existing row */
499    {
500      const int pos = peer - peers;
501      const int n_rhs = n_peers - (pos+1);
502      g_assert (n_rhs >= 0);
503
504      peer_row_set (store, &iter, peer);
505
506      /* remove it from the tr_peer_stat list */
507      g_memmove (peer, peer+1, sizeof(tr_peer_stat)*n_rhs);
508      --n_peers;
509    }
510    else if (!gtk_tree_store_remove (store, &iter))
511      break; /* we removed the model's last item */
512  }
513  while (gtk_tree_model_iter_next (model, &iter));
514
515  append_peers_to_model (store, peers, n_peers);  /* all these are new */
516
517  if (GDK_IS_DRAWABLE (p->completeness->window))
518    refresh_pieces (p->completeness, NULL, p->gtor);
519
520  fmtpeercount (p->seeders_lb, stat->seeders);
521  fmtpeercount (p->leechers_lb, stat->leechers);
522  fmtpeercount (p->completed_lb, stat->completedFromTracker );
523
524  free( peers );
525}
526
527static GtkWidget* peer_page_new ( TrTorrent * gtor )
528{
529  guint i;
530  GtkTreeModel *m;
531  GtkWidget *h, *v, *w, *ret, *da, *sw, *l, *vbox, *hbox;
532  tr_torrent * tor = tr_torrent_handle (gtor);
533  PeerData * p = g_new (PeerData, 1);
534  char name[64];
535
536  /* TODO: make this configurable? */
537  int view_columns[] = { PEER_COL_ADDRESS,
538                         PEER_COL_CLIENT,
539                         PEER_COL_PROGRESS,
540                         PEER_COL_IS_ENCRYPTED,
541                         PEER_COL_UPLOAD_RATE,
542                         PEER_COL_DOWNLOAD_RATE
543#if 0
544                         , PEER_COL_STATUS
545#endif
546                       };
547
548  m  = peer_model_new (tor);
549  v = gtk_tree_view_new_with_model (m);
550  gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(v), TRUE);
551  g_object_unref (G_OBJECT(m));
552
553  for (i=0; i<G_N_ELEMENTS(view_columns); ++i)
554  {
555    const int col = view_columns[i];
556    const char * t = _(peer_column_names[col]);
557    gboolean resizable = TRUE;
558    GtkTreeViewColumn * c;
559    GtkCellRenderer * r;
560
561    switch (col)
562    {
563      case PEER_COL_ADDRESS:
564        r = gtk_cell_renderer_text_new ();
565        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
566        break;
567
568      case PEER_COL_PORT:
569        r = gtk_cell_renderer_text_new ();
570        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
571        break;
572
573      case PEER_COL_CLIENT:
574        r = gtk_cell_renderer_text_new ();
575        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
576        gtk_tree_view_column_set_cell_data_func (c, r, render_client,
577                                                 NULL, NULL);
578        break;
579
580      case PEER_COL_PROGRESS:
581        r = gtk_cell_renderer_progress_new ();
582        c = gtk_tree_view_column_new_with_attributes (
583              _("Progress"), r, "value", PEER_COL_PROGRESS, NULL);
584        break;
585
586      case PEER_COL_IS_ENCRYPTED:
587        resizable = FALSE;
588        r = gtk_cell_renderer_pixbuf_new ();
589        c = gtk_tree_view_column_new_with_attributes (t, r, NULL);
590        gtk_tree_view_column_set_sizing (c, GTK_TREE_VIEW_COLUMN_FIXED);
591        gtk_tree_view_column_set_fixed_width (c, 20);
592        gtk_tree_view_column_set_cell_data_func (c, r, render_encrypted,
593                                                 NULL, NULL);
594        break;
595
596      case PEER_COL_IS_DOWNLOADING:
597        r = gtk_cell_renderer_text_new ();
598        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
599        break;
600
601      case PEER_COL_DOWNLOAD_RATE:
602        r = gtk_cell_renderer_text_new ();
603        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
604        gtk_tree_view_column_set_cell_data_func (c, r, render_dl_rate,
605                                                 NULL, NULL);
606        break;
607
608      case PEER_COL_IS_UPLOADING:
609        r = gtk_cell_renderer_text_new ();
610        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
611        break;
612
613      case PEER_COL_UPLOAD_RATE:
614        r = gtk_cell_renderer_text_new ();
615        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
616        gtk_tree_view_column_set_cell_data_func (c, r, render_ul_rate,
617                                                 NULL, NULL);
618        break;
619
620      case PEER_COL_STATUS:
621        r = gtk_cell_renderer_text_new( );
622        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
623        gtk_tree_view_column_set_cell_data_func (c, r, render_status, NULL, NULL);
624        break;
625       
626
627      default:
628        abort ();
629    }
630
631    gtk_tree_view_column_set_resizable (c, resizable);
632    gtk_tree_view_column_set_sort_column_id (c, col);
633    gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
634  }
635
636  /* the 'expander' column has a 10-pixel margin on the left
637     that doesn't look quite correct in any of these columns...
638     so create a non-visible column and assign it as the
639     'expander column. */
640  {
641    GtkTreeViewColumn *c = gtk_tree_view_column_new ();
642    gtk_tree_view_column_set_visible (c, FALSE);
643    gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
644    gtk_tree_view_set_expander_column (GTK_TREE_VIEW(v), c);
645  }
646
647  w = sw = gtk_scrolled_window_new (NULL, NULL);
648  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(w),
649                                  GTK_POLICY_NEVER,
650                                  GTK_POLICY_AUTOMATIC);
651  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(w),
652                                       GTK_SHADOW_IN);
653  gtk_container_add (GTK_CONTAINER(w), v);
654
655
656  vbox = gtk_vbox_new (FALSE, GUI_PAD);
657  gtk_container_set_border_width (GTK_CONTAINER(vbox), GUI_PAD_BIG);
658
659    g_snprintf (name, sizeof(name), "<b>%s</b>", _("Piece Availability"));
660    l = gtk_label_new (NULL);
661    gtk_misc_set_alignment (GTK_MISC(l), 0.0f, 0.5f);
662    gtk_label_set_markup (GTK_LABEL(l), name);
663    gtk_box_pack_start (GTK_BOX(vbox), l, FALSE, FALSE, 0);
664
665    w = da = p->completeness = gtk_drawing_area_new ();
666    gtk_widget_set_usize (w, 0u, 100u);
667    g_object_set_data (G_OBJECT(w), "draw-mode", GINT_TO_POINTER(DRAW_AVAIL));
668    g_signal_connect (w, "expose-event", G_CALLBACK(refresh_pieces), gtor);
669
670    h = gtk_hbox_new (FALSE, GUI_PAD);
671    w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
672    gtk_widget_set_usize (w, GUI_PAD_BIG, 0);
673    gtk_box_pack_start (GTK_BOX(h), w, FALSE, FALSE, 0);
674    gtk_box_pack_start_defaults (GTK_BOX(h), da);
675    gtk_box_pack_start (GTK_BOX(vbox), h, FALSE, FALSE, 0);
676
677    /* a small vertical spacer */
678    w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
679    gtk_widget_set_usize (w, 0u, GUI_PAD);
680    gtk_box_pack_start (GTK_BOX(vbox), w, FALSE, FALSE, 0);
681
682    g_snprintf (name, sizeof(name), "<b>%s</b>", _("Connected Peers"));
683    l = gtk_label_new (NULL);
684    gtk_misc_set_alignment (GTK_MISC(l), 0.0f, 0.5f);
685    gtk_label_set_markup (GTK_LABEL(l), name);
686    gtk_box_pack_start (GTK_BOX(vbox), l, FALSE, FALSE, 0);
687
688    h = gtk_hbox_new (FALSE, GUI_PAD);
689    w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
690    gtk_widget_set_usize (w, GUI_PAD_BIG, 0);
691    gtk_box_pack_start (GTK_BOX(h), w, FALSE, FALSE, 0);
692    gtk_box_pack_start_defaults (GTK_BOX(h), sw);
693    gtk_box_pack_start_defaults (GTK_BOX(vbox), h);
694
695    hbox = gtk_hbox_new (FALSE, GUI_PAD);
696    w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
697    gtk_widget_set_usize (w, GUI_PAD_BIG, 0);
698    gtk_box_pack_start (GTK_BOX(hbox), w, FALSE, FALSE, 0);
699        g_snprintf (name, sizeof(name), "<b>%s:</b>", _("Seeders"));
700        l = gtk_label_new (NULL);
701        gtk_label_set_markup (GTK_LABEL(l), name);
702        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
703        l = p->seeders_lb = gtk_label_new (NULL);
704        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
705    gtk_box_pack_start_defaults (GTK_BOX(hbox),
706                                 gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
707        g_snprintf (name, sizeof(name), "<b>%s:</b>", _("Leechers"));
708        l = gtk_label_new (NULL);
709        gtk_label_set_markup (GTK_LABEL(l), name);
710        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
711        l = p->leechers_lb = gtk_label_new (NULL);
712        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
713    gtk_box_pack_start_defaults (GTK_BOX(hbox),
714                                 gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
715        g_snprintf (name, sizeof(name), "<b>%s:</b>", _("Completed"));
716        l = gtk_label_new (NULL);
717        gtk_label_set_markup (GTK_LABEL(l), name);
718        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
719        l = p->completed_lb = gtk_label_new (NULL);
720        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
721    gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
722
723  ret = vbox;
724  p->gtor = gtor;
725  p->model = m;
726  p->store = GTK_TREE_STORE(m);
727  g_object_set_data_full (G_OBJECT(ret), "peer-data", p, g_free);
728  return ret;
729}
730
731/****
732*****  INFO TAB
733****/
734
735static GtkWidget* info_page_new (tr_torrent * tor)
736{
737  int row = 0;
738  GtkWidget *t = hig_workarea_create ();
739  GtkWidget *l, *w, *fr;
740  char *pch;
741  char *dname, *bname;
742  char sizeStr[128];
743  char buf[256];
744  char name[128];
745  const char * namefmt = "%s:";
746  GtkTextBuffer * b;
747  tr_tracker_info * track;
748  const tr_info * info = tr_torrentInfo(tor);
749
750  hig_workarea_add_section_title (t, &row, _("Torrent Information"));
751  hig_workarea_add_section_spacer (t, row, 5);
752
753    g_snprintf (name, sizeof(name), namefmt, _("Tracker"));
754    track = info->trackerList->list;
755    pch = track->port==80
756      ? g_strdup_printf( "http://%s%s", track->address, track->announce )
757      : g_strdup_printf( "http://%s:%d%s", track->address, track->port, track->announce );
758    l = gtk_label_new( pch );
759    gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
760
761    hig_workarea_add_row (t, &row, name, l, NULL);
762    g_free (pch);
763
764    g_snprintf (name, sizeof(name), namefmt, _("Pieces"));
765    tr_strlsize( sizeStr, info->pieceSize, sizeof(sizeStr) );
766    g_snprintf( buf, sizeof(buf), "%d (%s)", info->pieceCount, sizeStr );
767    l = gtk_label_new (buf);
768    hig_workarea_add_row (t, &row, name, l, NULL);
769
770    g_snprintf (name, sizeof(name), namefmt, _("Hash"));
771    l = gtk_label_new (info->hashString);
772    gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
773    hig_workarea_add_row (t, &row, name, l, NULL);
774
775    g_snprintf (name, sizeof(name), namefmt, _("Secure"));
776    pch = (info->isPrivate )
777      ? _("Private Torrent, PEX disabled")
778      : _("Public Torrent");
779    l = gtk_label_new (pch);
780    hig_workarea_add_row (t, &row, name, l, NULL);
781
782    g_snprintf (name, sizeof(name), namefmt, _("Comment"));
783    b = gtk_text_buffer_new (NULL);
784    gtk_text_buffer_set_text (b, info->comment, -1);
785    w = gtk_text_view_new_with_buffer (b);
786    gtk_widget_set_size_request (w, 0u, 100u);
787    gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW(w), GTK_WRAP_WORD); 
788    gtk_text_view_set_editable (GTK_TEXT_VIEW(w), FALSE);
789    fr = gtk_frame_new (NULL);
790    gtk_frame_set_shadow_type (GTK_FRAME(fr), GTK_SHADOW_IN);
791    gtk_container_add (GTK_CONTAINER(fr), w);
792    hig_workarea_add_row (t, &row, name, fr, NULL);
793
794  hig_workarea_add_section_divider (t, &row);
795  hig_workarea_add_section_title (t, &row, _("Created By"));
796  hig_workarea_add_section_spacer (t, row, 2);
797 
798    g_snprintf (name, sizeof(name), namefmt, _("Creator"));
799    l = gtk_label_new (*info->creator ? info->creator : _("N/A"));
800    hig_workarea_add_row (t, &row, name, l, NULL);
801
802    g_snprintf (name, sizeof(name), namefmt, _("Date"));
803    pch = rfc822date ((guint64)info->dateCreated * 1000u);
804    l = gtk_label_new (pch);
805    hig_workarea_add_row (t, &row, name, l, NULL); 
806    g_free (pch);
807
808  hig_workarea_add_section_divider (t, &row);
809  hig_workarea_add_section_title (t, &row, _("Location"));
810  hig_workarea_add_section_spacer (t, row, 3);
811
812    g_snprintf (name, sizeof(name), namefmt, _("Downloaded Data"));
813    l = gtk_label_new (tr_torrentGetFolder (tor));
814    gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
815    hig_workarea_add_row (t, &row, name, l, NULL); 
816
817    g_snprintf (name, sizeof(name), namefmt, _("Torrent File Path"));
818    dname = g_path_get_dirname (info->torrent);
819    l = gtk_label_new ( dname );
820    gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
821    hig_workarea_add_row (t, &row, name, l, NULL); 
822    g_free (dname);
823
824    g_snprintf (name, sizeof(name), namefmt, _("Torrent File Name"));
825    bname = g_path_get_basename (info->torrent);
826    l = gtk_label_new (bname);
827    gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
828    hig_workarea_add_row (t, &row, name, l, NULL); 
829    g_free (bname);
830
831  hig_workarea_finish (t, &row);
832  return t;
833}
834
835/****
836*****  ACTIVITY TAB
837****/
838
839typedef struct
840{
841  GtkWidget * state_lb;
842  GtkWidget * progress_lb;
843  GtkWidget * have_lb;
844  GtkWidget * dl_lb;
845  GtkWidget * ul_lb;
846  GtkWidget * failed_lb;
847  GtkWidget * ratio_lb;
848  GtkWidget * err_lb;
849  GtkWidget * swarm_lb;
850  GtkWidget * date_added_lb;
851  GtkWidget * last_activity_lb;
852  GtkWidget * availability_da;
853  TrTorrent * gtor;
854}
855Activity;
856
857static void
858refresh_activity (GtkWidget * top)
859{
860  Activity * a = (Activity*) g_object_get_data (G_OBJECT(top), "activity-data");
861  const tr_stat * stat = tr_torrent_stat( a->gtor );
862  char *pch;
863  char sizeStr[64];
864  char sizeStr2[64];
865  char buf[128];
866
867  pch = tr_torrent_status_str( a->gtor );
868  gtk_label_set_text (GTK_LABEL(a->state_lb), pch);
869  g_free (pch);
870
871  pch = g_strdup_printf( "%.1f%% (%.1f%% selected)", stat->percentComplete*100.0, stat->percentDone*100.0 );
872  gtk_label_set_text (GTK_LABEL(a->progress_lb), pch);
873  g_free (pch);
874
875  tr_strlsize( sizeStr,  stat->haveValid + stat->haveUnchecked, sizeof(sizeStr) );
876  tr_strlsize( sizeStr2, stat->haveValid,                       sizeof(sizeStr2) );
877  g_snprintf( buf, sizeof(buf), _("%s (%s verified)"), sizeStr, sizeStr2 );
878  gtk_label_set_text( GTK_LABEL( a->have_lb ), buf );
879
880  tr_strlsize( sizeStr, stat->downloadedEver, sizeof(sizeStr) );
881  gtk_label_set_text( GTK_LABEL(a->dl_lb), sizeStr );
882
883  tr_strlsize( sizeStr, stat->uploadedEver, sizeof(sizeStr) );
884  gtk_label_set_text( GTK_LABEL(a->ul_lb), sizeStr );
885
886  tr_strlsize( sizeStr, stat->corruptEver, sizeof(sizeStr) );
887  gtk_label_set_text (GTK_LABEL(a->failed_lb), sizeStr );
888
889  g_snprintf( buf, sizeof(buf), "%.1f", stat->ratio );
890  gtk_label_set_text (GTK_LABEL(a->ratio_lb), buf );
891
892  tr_strlspeed( buf, stat->swarmspeed, sizeof(buf) );
893  gtk_label_set_text (GTK_LABEL(a->swarm_lb), buf );
894
895  gtk_label_set_text (GTK_LABEL(a->err_lb),
896                      *stat->errorString ? stat->errorString : _("None"));
897
898  pch = stat->startDate ? rfc822date (stat->startDate)
899                        : g_strdup_printf ("?");
900  gtk_label_set_text (GTK_LABEL(a->date_added_lb), pch);
901  g_free (pch);
902
903  pch = stat->activityDate ? rfc822date (stat->activityDate)
904                           : g_strdup_printf ("?");
905  gtk_label_set_text (GTK_LABEL(a->last_activity_lb), pch);
906  g_free (pch);
907
908  if (GDK_IS_DRAWABLE (a->availability_da->window))
909    refresh_pieces (a->availability_da, NULL, a->gtor);
910}
911 
912
913static GtkWidget*
914activity_page_new (TrTorrent * gtor)
915{
916  Activity * a = g_new (Activity, 1);
917  int row = 0;
918  GtkWidget *t = hig_workarea_create ();
919  GtkWidget *l, *w;
920  char name[128];
921  const char * namefmt = "%s:";
922
923  a->gtor = gtor;
924
925  hig_workarea_add_section_title (t, &row, _("Transfer"));
926  hig_workarea_add_section_spacer (t, row, 8);
927
928    g_snprintf (name, sizeof(name), namefmt, _("State"));
929    l = a->state_lb = gtk_label_new (NULL);
930    hig_workarea_add_row (t, &row, name, l, NULL);
931
932    g_snprintf (name, sizeof(name), namefmt, _("Progress"));
933    l = a->progress_lb = gtk_label_new (NULL);
934    hig_workarea_add_row (t, &row, name, l, NULL);
935
936    g_snprintf (name, sizeof(name), namefmt, _("Have") );
937    l = a->have_lb = gtk_label_new (NULL);
938    hig_workarea_add_row (t, &row, name, l, NULL);
939
940    g_snprintf (name, sizeof(name), namefmt, _("Downloaded"));
941    l = a->dl_lb = gtk_label_new (NULL);
942    hig_workarea_add_row (t, &row, name, l, NULL);
943
944    g_snprintf (name, sizeof(name), namefmt, _("Uploaded"));
945    l = a->ul_lb = gtk_label_new (NULL);
946    hig_workarea_add_row (t, &row, name, l, NULL);
947
948    g_snprintf (name, sizeof(name), namefmt, _("Failed DL"));
949    l = a->failed_lb = gtk_label_new (NULL);
950    hig_workarea_add_row (t, &row, name, l, NULL);
951
952    g_snprintf (name, sizeof(name), namefmt, _("Ratio"));
953    l = a->ratio_lb = gtk_label_new (NULL);
954    hig_workarea_add_row (t, &row, name, l, NULL);
955
956    g_snprintf (name, sizeof(name), namefmt, _("Swarm Rate"));
957    l = a->swarm_lb = gtk_label_new (NULL);
958    hig_workarea_add_row (t, &row, name, l, NULL);
959
960    g_snprintf (name, sizeof(name), namefmt, _("Error"));
961    l = a->err_lb = gtk_label_new (NULL);
962    hig_workarea_add_row (t, &row, name, l, NULL);
963
964    g_snprintf (name, sizeof(name), namefmt, _("Completeness"));
965    w = a->availability_da = gtk_drawing_area_new ();
966    gtk_widget_set_usize (w, 0u, 100u);
967    g_object_set_data (G_OBJECT(w), "draw-mode", GINT_TO_POINTER(DRAW_PROG));
968    g_signal_connect (w, "expose-event", G_CALLBACK(refresh_pieces), gtor);
969    hig_workarea_add_row (t, &row, name, w, NULL);
970
971  hig_workarea_add_section_divider (t, &row);
972  hig_workarea_add_section_title (t, &row, _("Dates"));
973  hig_workarea_add_section_spacer (t, row, 3);
974
975    g_snprintf (name, sizeof(name), namefmt, _("Added"));
976    l = a->date_added_lb = gtk_label_new (NULL);
977    hig_workarea_add_row (t, &row, name, l, NULL);
978
979    g_snprintf (name, sizeof(name), namefmt, _("Last Activity"));
980    l = a->last_activity_lb = gtk_label_new (NULL);
981    hig_workarea_add_row (t, &row, name, l, NULL);
982
983  hig_workarea_add_section_divider (t, &row);
984  hig_workarea_finish (t, &row);
985  g_object_set_data_full (G_OBJECT(t), "activity-data", a, g_free);
986  return t;
987}
988
989/****
990*****  FILES TAB
991****/
992
993#define STRIPROOT( path )                                                     \
994    ( g_path_is_absolute( (path) ) ? g_path_skip_root( (path) ) : (path) )
995
996enum
997{
998  FC_STOCK,
999  FC_LABEL,
1000  FC_PROG,
1001  FC_KEY,
1002  FC_INDEX,
1003  FC_SIZE,
1004  FC_PRIORITY,
1005  FC_ENABLED,
1006  N_FILE_COLS
1007};
1008
1009typedef struct
1010{
1011  TrTorrent * gtor;
1012  GtkTreeModel * model; /* same object as store, but recast */
1013  GtkTreeStore * store; /* same object as model, but recast */
1014  GtkTreeSelection * selection;
1015}
1016FileData;
1017
1018static const char*
1019priorityToString( const int priority )
1020{
1021    switch( priority ) {
1022        case TR_PRI_HIGH:   return _("High");
1023        case TR_PRI_NORMAL: return _("Normal");
1024        case TR_PRI_LOW:    return _("Low");
1025        default:            return "BUG!";
1026    }
1027}
1028
1029static tr_priority_t
1030stringToPriority( const char* str )
1031{
1032    if( !strcmp( str, _( "High" ) ) ) return TR_PRI_HIGH;
1033    if( !strcmp( str, _( "Low" ) ) ) return TR_PRI_LOW;
1034    return TR_PRI_NORMAL;
1035}
1036
1037static void
1038parsepath( const tr_torrent  * tor,
1039           GtkTreeStore      * store,
1040           GtkTreeIter       * ret,
1041           const char        * path,
1042           int                 index,
1043           uint64_t            size )
1044{
1045    GtkTreeModel * model;
1046    GtkTreeIter  * parent, start, iter;
1047    char         * file, * lower, * mykey, *escaped=0;
1048    const char   * stock;
1049    int            priority = 0;
1050    gboolean       enabled = TRUE;
1051
1052    model  = GTK_TREE_MODEL( store );
1053    parent = NULL;
1054    file   = g_path_get_basename( path );
1055    if( 0 != strcmp( file, path ) )
1056    {
1057        char * dir = g_path_get_dirname( path );
1058        parsepath( tor, store, &start, dir, index, size );
1059        parent = &start;
1060        g_free( dir );
1061    }
1062
1063    lower = g_utf8_casefold( file, -1 );
1064    mykey = g_utf8_collate_key( lower, -1 );
1065    if( gtk_tree_model_iter_children( model, &iter, parent ) ) do
1066    {
1067        gboolean stop;
1068        char * modelkey;
1069        gtk_tree_model_get( model, &iter, FC_KEY, &modelkey, -1 );
1070        stop = (modelkey!=NULL) && !strcmp(mykey,modelkey);
1071        g_free (modelkey);
1072        if (stop) goto done;
1073    }
1074    while( gtk_tree_model_iter_next( model, &iter ) );
1075
1076    gtk_tree_store_append( store, &iter, parent );
1077    if( NULL == ret )
1078    {
1079        stock = GTK_STOCK_FILE;
1080    }
1081    else
1082    {
1083        stock = GTK_STOCK_DIRECTORY;
1084        size  = 0;
1085        index = -1;
1086    }
1087
1088    if (index != -1) {
1089        priority = tr_torrentGetFilePriority( tor, index );
1090        enabled  = tr_torrentGetFileDL( tor, index );
1091    }
1092
1093    escaped = g_markup_escape_text (file, -1); 
1094    gtk_tree_store_set( store, &iter, FC_INDEX, index,
1095                                      FC_LABEL, escaped,
1096                                      FC_KEY, mykey,
1097                                      FC_STOCK, stock,
1098                                      FC_PRIORITY, priorityToString(priority),
1099                                      FC_ENABLED, enabled,
1100                                      FC_SIZE, size, -1 );
1101  done:
1102    g_free( escaped );
1103    g_free( mykey );
1104    g_free( lower );
1105    g_free( file );
1106    if( NULL != ret )
1107      *ret = iter;
1108}
1109
1110static uint64_t
1111getdirtotals( GtkTreeStore * store, GtkTreeIter * parent )
1112{
1113    GtkTreeModel * model;
1114    GtkTreeIter    iter;
1115    uint64_t       mysize, subsize;
1116    char         * name, * label;
1117
1118    model  = GTK_TREE_MODEL( store );
1119    mysize = 0;
1120    if( gtk_tree_model_iter_children( model, &iter, parent ) ) do
1121    {
1122         char sizeStr[64];
1123        if( gtk_tree_model_iter_has_child( model, &iter ) )
1124        {
1125            subsize = getdirtotals( store, &iter );
1126            gtk_tree_store_set( store, &iter, FC_SIZE, subsize, -1 );
1127        }
1128        else
1129        {
1130            gtk_tree_model_get( model, &iter, FC_SIZE, &subsize, -1 );
1131        }
1132        gtk_tree_model_get( model, &iter, FC_LABEL, &name, -1 );
1133        tr_strlsize( sizeStr, subsize, sizeof( sizeStr ) );
1134        label = g_markup_printf_escaped( "<small>%s (%s)</small>",
1135                                          name, sizeStr );
1136        g_free( name );
1137        gtk_tree_store_set( store, &iter, FC_LABEL, label, -1 );
1138        g_free( label );
1139        mysize += subsize;
1140    }
1141    while( gtk_tree_model_iter_next( model, &iter ) );
1142
1143    return mysize;
1144}
1145
1146static void
1147updateprogress( GtkTreeModel   * model,
1148                GtkTreeStore   * store,
1149                GtkTreeIter    * parent,
1150                tr_file_stat   * fileStats,
1151                guint64        * setmeGotSize,
1152                guint64        * setmeTotalSize)
1153{
1154    GtkTreeIter iter;
1155    guint64 gotSize=0, totalSize=0;
1156
1157    if( gtk_tree_model_iter_children( model, &iter, parent ) ) do
1158    {
1159        int oldProg, newProg;
1160        guint64 subGot, subTotal;
1161
1162        if (gtk_tree_model_iter_has_child( model, &iter ) )
1163        {
1164            updateprogress( model, store, &iter, fileStats, &subGot, &subTotal);
1165        }
1166        else
1167        {
1168            int index, percent;
1169            gtk_tree_model_get( model, &iter, FC_SIZE, &subTotal,
1170                                              FC_INDEX, &index,
1171                                              -1 );
1172            g_assert( 0 <= index );
1173            percent = (int)(fileStats[index].progress * 100.0); /* [0...100] */
1174            subGot = (guint64)(subTotal * percent/100.0);
1175        }
1176
1177        if (!subTotal) subTotal = 1; /* avoid div by zero */
1178        g_assert (subGot <= subTotal);
1179
1180        /* why not just set it every time?
1181           because that causes the "priorities" combobox to pop down */
1182        gtk_tree_model_get (model, &iter, FC_PROG, &oldProg, -1);
1183        newProg = (int)(100.0*subGot/subTotal);
1184        if (oldProg != newProg)
1185          gtk_tree_store_set (store, &iter,
1186                              FC_PROG, (int)(100.0*subGot/subTotal), -1);
1187
1188        gotSize += subGot;
1189        totalSize += subTotal;
1190    }
1191    while( gtk_tree_model_iter_next( model, &iter ) );
1192
1193    *setmeGotSize = gotSize;
1194    *setmeTotalSize = totalSize;
1195}
1196
1197static GtkTreeModel*
1198priority_model_new (void)
1199{
1200  GtkTreeIter iter;
1201  GtkListStore * store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
1202  gtk_list_store_append (store, &iter);
1203  gtk_list_store_set (store, &iter, 0, _("High"), 1, TR_PRI_HIGH, -1);
1204  gtk_list_store_append (store, &iter);
1205  gtk_list_store_set (store, &iter, 0, _("Normal"), 1, TR_PRI_NORMAL, -1);
1206  gtk_list_store_append (store, &iter);
1207  gtk_list_store_set (store, &iter, 0, _("Low"), 1, TR_PRI_LOW, -1);
1208  return GTK_TREE_MODEL (store);
1209}
1210
1211static void
1212refreshPriorityActions( GtkTreeSelection * sel )
1213{
1214    GtkTreeIter iter;
1215    GtkTreeModel * model;
1216    const gboolean has_selection = gtk_tree_selection_get_selected( sel, &model, &iter );
1217
1218    action_sensitize ( "priority-high", has_selection );
1219    action_sensitize ( "priority-normal", has_selection );
1220    action_sensitize ( "priority-low", has_selection );
1221
1222    if( has_selection )
1223    {
1224        /* set the action priority base on the model's values */
1225        char * pch = NULL;
1226        const char * key;
1227        gtk_tree_model_get( model, &iter, FC_PRIORITY, &pch, -1 );
1228        switch( stringToPriority( pch ) ) {
1229            case TR_PRI_HIGH:   key = "priority-high";   break;
1230            case TR_PRI_LOW:    key = "priority-low";    break;
1231            default:            key = "priority-normal"; break;
1232        }
1233        action_toggle( key, TRUE );
1234        g_free( pch );
1235    }
1236}
1237
1238static void
1239subtree_walk_dnd( GtkTreeStore   * store,
1240                  GtkTreeIter    * iter,
1241                  tr_torrent     * tor,
1242                  gboolean         enabled,
1243                  GArray         * indices )
1244{
1245    int index;
1246    GtkTreeIter child;
1247 
1248    /* update this node */ 
1249    gtk_tree_model_get( GTK_TREE_MODEL(store), iter, FC_INDEX, &index, -1  );
1250    if (index >= 0)
1251      g_array_append_val( indices, index );
1252    gtk_tree_store_set( store, iter, FC_ENABLED, enabled, -1 );
1253
1254    /* visit the children */
1255    if( gtk_tree_model_iter_children( GTK_TREE_MODEL(store), &child, iter ) ) do
1256      subtree_walk_dnd( store, &child, tor, enabled, indices );
1257    while( gtk_tree_model_iter_next( GTK_TREE_MODEL(store), &child ) );
1258}
1259
1260static void
1261set_subtree_dnd( GtkTreeStore   * store,
1262                 GtkTreeIter    * iter,
1263                 tr_torrent     * tor,
1264                 gboolean         enabled )
1265{
1266    GArray * indices = g_array_new( FALSE, FALSE, sizeof(int) );
1267    subtree_walk_dnd( store, iter, tor, enabled, indices );
1268    tr_torrentSetFileDLs( tor, (int*)indices->data, (int)indices->len, enabled );
1269    g_array_free( indices, TRUE );
1270}
1271
1272static void
1273subtree_walk_priority( GtkTreeStore   * store,
1274                       GtkTreeIter    * iter,
1275                       tr_torrent     * tor,
1276                       int              priority,
1277                       GArray         * indices )
1278{
1279    int index;
1280    GtkTreeIter child;
1281
1282    /* update this node */ 
1283    gtk_tree_model_get( GTK_TREE_MODEL(store), iter, FC_INDEX, &index, -1  );
1284    if( index >= 0 )
1285        g_array_append_val( indices, index );
1286    gtk_tree_store_set( store, iter, FC_PRIORITY, priorityToString(priority), -1 );
1287
1288    /* visit the children */
1289    if( gtk_tree_model_iter_children( GTK_TREE_MODEL(store), &child, iter ) ) do
1290        subtree_walk_priority( store, &child, tor, priority, indices );
1291    while( gtk_tree_model_iter_next( GTK_TREE_MODEL(store), &child ) );
1292
1293}
1294
1295static void
1296set_subtree_priority( GtkTreeStore * store,
1297                      GtkTreeIter * iter,
1298                      tr_torrent * tor,
1299                      int priority,
1300                      GtkTreeSelection * selection )
1301{
1302    GArray * indices = g_array_new( FALSE, FALSE, sizeof(int) );
1303    subtree_walk_priority( store, iter, tor, priority, indices );
1304    tr_torrentSetFilePriorities( tor, (int*)indices->data, (int)indices->len, priority );
1305    g_array_free( indices, TRUE );
1306
1307    refreshPriorityActions( selection );
1308}
1309
1310static void
1311priority_changed_cb (GtkCellRendererText * cell UNUSED,
1312                     const gchar         * path,
1313                     const gchar         * value,
1314                     void                * file_data)
1315{
1316    GtkTreeIter iter;
1317    FileData * d = (FileData*) file_data;
1318    if (gtk_tree_model_get_iter_from_string (d->model, &iter, path))
1319    {
1320        tr_torrent  * tor = tr_torrent_handle( d->gtor );
1321        const tr_priority_t priority = stringToPriority( value );
1322        set_subtree_priority( d->store, &iter, tor, priority, d->selection );
1323    }
1324}
1325
1326/* FIXME: NULL this back out when popup goes down */
1327static GtkWidget * popupView = NULL;
1328
1329static void
1330on_popup_menu ( GtkWidget * view, GdkEventButton * event )
1331{
1332    GtkWidget * menu = action_get_widget ( "/file-popup" );
1333    popupView = view;
1334    gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL,
1335                    (event ? event->button : 0),
1336                    (event ? event->time : 0));
1337}
1338
1339static void
1340fileSelectionChangedCB( GtkTreeSelection * sel, gpointer unused UNUSED )
1341{
1342    refreshPriorityActions( sel );
1343}
1344
1345void
1346set_selected_file_priority ( tr_priority_t priority_val )
1347{
1348    if( popupView && GTK_IS_TREE_VIEW(popupView) )
1349    {
1350        GtkTreeView * view = GTK_TREE_VIEW( popupView );
1351        tr_torrent * tor = g_object_get_data (G_OBJECT(view), "torrent-handle");
1352        GtkTreeModel * model;
1353        GtkTreeIter iter;
1354        GtkTreeSelection * sel = gtk_tree_view_get_selection (view);
1355        gtk_tree_selection_get_selected( sel, &model, &iter );
1356
1357        set_subtree_priority( GTK_TREE_STORE(model), &iter,
1358                              tor, priority_val, sel );
1359    }
1360}
1361
1362static void
1363enabled_toggled (GtkCellRendererToggle  * cell UNUSED,
1364                 const gchar            * path_str,
1365                 gpointer                 data_gpointer)
1366{
1367  FileData * data = (FileData*) data_gpointer;
1368  GtkTreePath * path = gtk_tree_path_new_from_string( path_str );
1369  GtkTreeModel * model = data->model;
1370  GtkTreeIter iter;
1371  gboolean enabled;
1372
1373  gtk_tree_model_get_iter( model, &iter, path );
1374  gtk_tree_model_get( model, &iter, FC_ENABLED, &enabled, -1 );
1375  enabled = !enabled;
1376  set_subtree_dnd( GTK_TREE_STORE(model),
1377                   &iter,
1378                   tr_torrent_handle( data->gtor ),
1379                   enabled );
1380
1381  gtk_tree_path_free( path );
1382}
1383
1384GtkWidget *
1385file_page_new ( TrTorrent * gtor )
1386{
1387    GtkWidget           * ret;
1388    FileData            * data;
1389    const tr_info       * inf;
1390    tr_torrent          * tor;
1391    GtkTreeStore        * store;
1392    int                   ii;
1393    GtkWidget           * view, * scroll;
1394    GtkCellRenderer     * rend;
1395    GtkCellRenderer     * priority_rend;
1396    GtkCellRenderer     * enabled_rend;
1397    GtkTreeViewColumn   * col;
1398    GtkTreeSelection    * sel;
1399    GtkTreeModel        * model;
1400
1401    store = gtk_tree_store_new ( N_FILE_COLS,
1402                                 G_TYPE_STRING,    /* stock */
1403                                 G_TYPE_STRING,    /* label */
1404                                 G_TYPE_INT,       /* prog [0..100] */
1405                                 G_TYPE_STRING,    /* key */
1406                                 G_TYPE_INT,       /* index */
1407                                 G_TYPE_UINT64,    /* size */
1408                                 G_TYPE_STRING,    /* priority */
1409                                 G_TYPE_BOOLEAN ); /* dl enabled */
1410
1411    /* set up the model */
1412    tor = tr_torrent_handle( gtor );
1413    inf = tr_torrent_info( gtor );
1414    for( ii = 0; ii < inf->fileCount; ii++ )
1415    {
1416        parsepath( tor, store, NULL, STRIPROOT( inf->files[ii].name ),
1417                   ii, inf->files[ii].length );
1418    }
1419    getdirtotals( store, NULL );
1420
1421    /* create the view */
1422    view = gtk_tree_view_new_with_model( GTK_TREE_MODEL( store ) );
1423    g_object_set_data (G_OBJECT(view), "torrent-handle", tor );
1424    g_signal_connect( view, "popup-menu",
1425                      G_CALLBACK(on_popup_menu), NULL );
1426    g_signal_connect( view, "button-press-event",
1427                      G_CALLBACK(on_tree_view_button_pressed), (void*) on_popup_menu);
1428
1429    /* add file column */
1430   
1431    col = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
1432        "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
1433        "expand", TRUE,
1434        "title", _("File"),
1435        NULL));
1436    rend = gtk_cell_renderer_pixbuf_new();
1437    gtk_tree_view_column_pack_start( col, rend, FALSE );
1438    gtk_tree_view_column_add_attribute( col, rend, "stock-id", FC_STOCK );
1439    /* add text renderer */
1440    rend = gtk_cell_renderer_text_new();
1441    g_object_set( rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
1442    gtk_tree_view_column_pack_start( col, rend, TRUE );
1443    gtk_tree_view_column_add_attribute( col, rend, "markup", FC_LABEL );
1444    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
1445    /* add progress column */
1446    rend = gtk_cell_renderer_progress_new();
1447    col = gtk_tree_view_column_new_with_attributes (
1448      _("Progress"), rend, "value", FC_PROG, NULL);
1449    gtk_tree_view_column_set_sort_column_id( col, FC_PROG );
1450    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
1451    /* set up view */
1452    sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
1453    gtk_tree_view_expand_all( GTK_TREE_VIEW( view ) );
1454    gtk_tree_view_set_search_column( GTK_TREE_VIEW( view ), FC_LABEL );
1455    g_signal_connect( sel, "changed", G_CALLBACK(fileSelectionChangedCB), NULL );
1456    fileSelectionChangedCB( sel, NULL );
1457
1458    /* add "download" checkbox column */
1459    col = gtk_tree_view_column_new ();
1460    gtk_tree_view_column_set_sort_column_id( col, FC_ENABLED );
1461    rend = enabled_rend = gtk_cell_renderer_toggle_new  ();
1462    col = gtk_tree_view_column_new_with_attributes (_("Download"),
1463                                                    rend,
1464                                                    "active", FC_ENABLED,
1465                                                    NULL);
1466    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
1467
1468    /* add priority column */
1469    model = priority_model_new ();
1470    col = gtk_tree_view_column_new ();
1471    gtk_tree_view_column_set_sort_column_id( col, FC_PRIORITY );
1472    gtk_tree_view_column_set_title (col, _("Priority"));
1473    rend = priority_rend = gtk_cell_renderer_combo_new ();
1474    gtk_tree_view_column_pack_start (col, rend, TRUE);
1475    g_object_set (G_OBJECT(rend), "model", model,
1476                                  "editable", FALSE,
1477                                  "has-entry", FALSE,
1478                                  "text-column", 0,
1479                                  NULL);
1480    g_object_unref (G_OBJECT(model));
1481    gtk_tree_view_column_add_attribute (col, rend, "text", FC_PRIORITY);
1482    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
1483
1484    /* create the scrolled window and stick the view in it */
1485    scroll = gtk_scrolled_window_new( NULL, NULL );
1486    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scroll ),
1487                                    GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
1488    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scroll),
1489                                         GTK_SHADOW_IN);
1490    gtk_container_add( GTK_CONTAINER( scroll ), view );
1491    gtk_widget_set_usize (scroll, 0u, 200u);
1492    gtk_container_set_border_width (GTK_CONTAINER(scroll), GUI_PAD);
1493
1494    ret = scroll;
1495    data = g_new (FileData, 1);
1496    data->gtor = gtor;
1497    data->model = GTK_TREE_MODEL(store);
1498    data->store = store;
1499    data->selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
1500    g_object_set_data_full (G_OBJECT(ret), "file-data", data, g_free);
1501    g_signal_connect (G_OBJECT(priority_rend), "edited", G_CALLBACK(priority_changed_cb), data);
1502    g_signal_connect(enabled_rend, "toggled", G_CALLBACK(enabled_toggled), data );
1503    return ret;
1504}
1505
1506static void
1507refresh_files (GtkWidget * top)
1508{
1509    guint64 foo, bar;
1510    int fileCount = 0;
1511    FileData * data = (FileData*) g_object_get_data (G_OBJECT(top), "file-data");
1512    tr_torrent * tor = tr_torrent_handle( data->gtor );
1513    tr_file_stat * fileStats = tr_torrentFiles( tor, &fileCount );
1514    updateprogress (data->model, data->store, NULL, fileStats, &foo, &bar);
1515    tr_torrentFilesFree( fileStats, fileCount );
1516}
1517
1518
1519/****
1520*****  OPTIONS
1521****/
1522
1523static void
1524speed_toggled_cb( GtkToggleButton * tb, gpointer gtor, int up_or_down )
1525{
1526  tr_torrent * tor = tr_torrent_handle (gtor);
1527  gboolean b = gtk_toggle_button_get_active(tb);
1528  tr_torrentSetSpeedMode( tor, up_or_down, b ? TR_SPEEDLIMIT_SINGLE
1529                                             : TR_SPEEDLIMIT_GLOBAL );
1530}
1531static void
1532ul_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
1533{
1534  speed_toggled_cb( tb, gtor, TR_UP );
1535}
1536static void
1537dl_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
1538{
1539  speed_toggled_cb( tb, gtor, TR_DOWN );
1540}
1541
1542
1543#if 0
1544static void
1545seeding_cap_toggled_cb (GtkToggleButton *tb, gpointer gtor)
1546{
1547  tr_torrent_set_seeding_cap_enabled (TR_TORRENT(gtor),
1548                                      gtk_toggle_button_get_active(tb));
1549}
1550#endif
1551
1552static void
1553sensitize_from_check_cb (GtkToggleButton *toggle, gpointer w)
1554{
1555  gtk_widget_set_sensitive (GTK_WIDGET(w),
1556                            gtk_toggle_button_get_active(toggle));
1557}
1558
1559static void
1560setSpeedLimit( GtkSpinButton* spin, gpointer gtor, int up_or_down )
1561{
1562  tr_torrent * tor = tr_torrent_handle (gtor);
1563  int KiB_sec = gtk_spin_button_get_value_as_int (spin);
1564  tr_torrentSetSpeedLimit( tor, up_or_down, KiB_sec );
1565}
1566static void
1567ul_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
1568{
1569  setSpeedLimit( spin, gtor, TR_UP );
1570}
1571static void
1572dl_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
1573{
1574  setSpeedLimit( spin, gtor, TR_DOWN );
1575}
1576
1577#if 0
1578static void
1579seeding_ratio_spun_cb (GtkSpinButton *spin, gpointer gtor)
1580{
1581  tr_torrent_set_seeding_cap_ratio (TR_TORRENT(gtor),
1582                                    gtk_spin_button_get_value(spin));
1583}
1584#endif
1585
1586static void
1587max_peers_spun_cb( GtkSpinButton * spin, gpointer gtor )
1588{
1589  const uint16_t n = gtk_spin_button_get_value( spin );
1590  tr_torrentSetMaxConnectedPeers( tr_torrent_handle( gtor ), n );
1591}
1592
1593GtkWidget*
1594options_page_new ( TrTorrent * gtor )
1595{
1596  uint16_t maxConnectedPeers;
1597  int i, row;
1598  gboolean b;
1599  GtkAdjustment *a;
1600  GtkWidget *t, *w, *tb;
1601  tr_torrent * tor = tr_torrent_handle (gtor);
1602
1603  row = 0;
1604  t = hig_workarea_create ();
1605  hig_workarea_add_section_title (t, &row, _("Speed Limits") );
1606  hig_workarea_add_section_spacer (t, row, 2);
1607
1608    tb = gtk_check_button_new_with_mnemonic (_("Limit _Download Speed (KiB/s):"));
1609    b = tr_torrentGetSpeedMode(tor,TR_DOWN) == TR_SPEEDLIMIT_SINGLE;
1610    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), b );
1611    g_signal_connect (tb, "toggled", G_CALLBACK(dl_speed_toggled_cb), gtor);
1612
1613    i = tr_torrentGetSpeedLimit( tor, TR_DOWN );
1614    a = (GtkAdjustment*) gtk_adjustment_new (i, 0.0, G_MAXDOUBLE, 1, 1, 1);
1615    w = gtk_spin_button_new (a, 1, 0);
1616    g_signal_connect (w, "value-changed", G_CALLBACK(dl_speed_spun_cb), gtor);
1617    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1618    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1619    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1620
1621    tb = gtk_check_button_new_with_mnemonic (_("Limit _Upload Speed (KiB/s):"));
1622    b = tr_torrentGetSpeedMode(tor,TR_UP) == TR_SPEEDLIMIT_SINGLE;
1623    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), b );
1624    g_signal_connect (tb, "toggled", G_CALLBACK(ul_speed_toggled_cb), gtor);
1625
1626    i = tr_torrentGetSpeedLimit( tor, TR_UP );
1627    a = (GtkAdjustment*) gtk_adjustment_new (i, 0.0, G_MAXDOUBLE, 1, 1, 1);
1628    w = gtk_spin_button_new (a, 1, 0);
1629    g_signal_connect (w, "value-changed", G_CALLBACK(ul_speed_spun_cb), gtor);
1630    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1631    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1632    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1633
1634  hig_workarea_add_section_divider (t, &row);
1635  hig_workarea_add_section_title (t, &row, _("Peer Connections"));
1636  hig_workarea_add_section_spacer (t, row, 1);
1637
1638    maxConnectedPeers = tr_torrentGetMaxConnectedPeers( tor );
1639    w = gtk_spin_button_new_with_range( 1, 3000, 5 );
1640    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), maxConnectedPeers );
1641    hig_workarea_add_row( t, &row, _( "Maximum connected peers:" ), w, NULL );
1642    g_signal_connect( w, "value-changed", G_CALLBACK( max_peers_spun_cb ), gtor );
1643
1644#if 0
1645    tb = gtk_check_button_new_with_mnemonic (_("_Stop Seeding at Ratio:"));
1646    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), gtor->seeding_cap_enabled);
1647    g_signal_connect (tb, "toggled", G_CALLBACK(seeding_cap_toggled_cb), gtor);
1648    a = (GtkAdjustment*) gtk_adjustment_new (gtor->seeding_cap, 0.0, G_MAXDOUBLE, 1, 1, 1);
1649    w = gtk_spin_button_new (a, 1, 1);
1650    g_signal_connect (w, "value-changed", G_CALLBACK(seeding_ratio_spun_cb), gtor);
1651    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1652    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1653    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1654#endif
1655
1656  hig_workarea_finish (t, &row);
1657  return t;
1658}
1659
1660static void
1661refresh_options (GtkWidget * top UNUSED)
1662{
1663}
1664
1665/****
1666*****  DIALOG
1667****/
1668
1669static void
1670torrent_destroyed (gpointer dialog, GObject * dead_torrent UNUSED)
1671{
1672  gtk_widget_destroy (GTK_WIDGET(dialog));
1673}
1674
1675static void
1676remove_tag (gpointer tag)
1677{
1678  g_source_remove (GPOINTER_TO_UINT(tag)); /* stop the periodic refresh */
1679}
1680
1681static void
1682response_cb (GtkDialog *dialog, int response UNUSED, gpointer gtor)
1683{
1684  g_object_weak_unref (G_OBJECT(gtor), torrent_destroyed, dialog);
1685  gtk_widget_destroy (GTK_WIDGET(dialog));
1686}
1687
1688static gboolean
1689periodic_refresh (gpointer data)
1690{
1691  refresh_peers    (g_object_get_data (G_OBJECT(data), "peers-top"));
1692  refresh_activity (g_object_get_data (G_OBJECT(data), "activity-top"));
1693  refresh_files    (g_object_get_data (G_OBJECT(data), "files-top"));
1694  refresh_options  (g_object_get_data (G_OBJECT(data), "options-top"));
1695
1696  return TRUE;
1697}
1698
1699GtkWidget*
1700torrent_inspector_new ( GtkWindow * parent, TrTorrent * gtor )
1701{
1702  guint tag;
1703  GtkWidget *d, *n, *w;
1704  tr_torrent * tor = tr_torrent_handle (gtor);
1705  char sizeStr[64];
1706  char title[512];
1707  const tr_info * info = tr_torrent_info (gtor);
1708
1709  /* create the dialog */
1710  tr_strlsize( sizeStr, info->totalSize, sizeof(sizeStr) );
1711  g_snprintf( title, sizeof(title), _( "Details for %s (%s)" ), info->name, sizeStr );
1712  d = gtk_dialog_new_with_buttons (title, parent, 0,
1713                                   GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1714                                   NULL);
1715  gtk_window_set_role (GTK_WINDOW(d), "tr-info" );
1716  g_signal_connect (d, "response", G_CALLBACK (response_cb), gtor);
1717  g_object_weak_ref (G_OBJECT(gtor), torrent_destroyed, d);
1718
1719  /* add the notebook */
1720  n = gtk_notebook_new ();
1721
1722  w = activity_page_new (gtor);
1723  g_object_set_data (G_OBJECT(d), "activity-top", w);
1724  gtk_notebook_append_page (GTK_NOTEBOOK(n), w, 
1725                            gtk_label_new_with_mnemonic (_("_Activity")));
1726
1727  w = peer_page_new (gtor);
1728  g_object_set_data (G_OBJECT(d), "peers-top", w);
1729  gtk_notebook_append_page (GTK_NOTEBOOK(n),  w,
1730                            gtk_label_new_with_mnemonic (_("_Peers")));
1731
1732  gtk_notebook_append_page (GTK_NOTEBOOK(n),
1733                            info_page_new (tor),
1734                            gtk_label_new_with_mnemonic (_("_Info")));
1735
1736  w = file_page_new (gtor);
1737  g_object_set_data (G_OBJECT(d), "files-top", w);
1738  gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
1739                            gtk_label_new_with_mnemonic (_("_Files")));
1740
1741  w = options_page_new (gtor);
1742  g_object_set_data (G_OBJECT(d), "options-top", w);
1743  gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
1744                            gtk_label_new_with_mnemonic (_("_Options")));
1745
1746  gtk_box_pack_start_defaults (GTK_BOX(GTK_DIALOG(d)->vbox), n);
1747
1748  tag = g_timeout_add (UPDATE_INTERVAL_MSEC, periodic_refresh, d);
1749  g_object_set_data_full (G_OBJECT(d), "tag",
1750                          GUINT_TO_POINTER(tag), remove_tag);
1751
1752  /* return the results */
1753  periodic_refresh (d);
1754  gtk_widget_show_all (GTK_DIALOG(d)->vbox);
1755  return d;
1756}
Note: See TracBrowser for help on using the repository browser.