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

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

undo previous commit

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