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

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