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

Last change on this file since 2425 was 2425, checked in by joshe, 15 years ago

Include stdio.h a couple places where it could be needed.

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