1 /*
2 * This file Copyright (C) 2007-2014 Mnemosyne LLC
3 *
4 * It may be used under the GNU GPL versions 2 or 3
5 * or any future license endorsed by Mnemosyne LLC.
6 *
7 */
8
9 #include <limits.h> /* INT_MAX */
10 #include <gtk/gtk.h>
11 #include <glib/gi18n.h>
12 #include <libtransmission/transmission.h>
13 #include <libtransmission/utils.h> /* tr_truncd() */
14 #include "hig.h"
15 #include "icons.h"
16 #include "torrent-cell-renderer.h"
17 #include "util.h"
18
19 /* #define TEST_RTL */
20
21 enum
22 {
23 P_TORRENT = 1,
24 P_UPLOAD_SPEED,
25 P_DOWNLOAD_SPEED,
26 P_BAR_HEIGHT,
27 P_COMPACT
28 };
29
30 #define DEFAULT_BAR_HEIGHT 12
31 #define SMALL_SCALE 0.9
32 #define COMPACT_ICON_SIZE GTK_ICON_SIZE_MENU
33 #define FULL_ICON_SIZE GTK_ICON_SIZE_DND
34
35 /***
36 ****
37 ***/
38
getProgressString(GString * gstr,tr_torrent const * tor,tr_info const * info,tr_stat const * st)39 static void getProgressString(GString* gstr, tr_torrent const* tor, tr_info const* info, tr_stat const* st)
40 {
41 bool const isDone = st->leftUntilDone == 0;
42 uint64_t const haveTotal = st->haveUnchecked + st->haveValid;
43 bool const isSeed = st->haveValid >= info->totalSize;
44 char buf1[32];
45 char buf2[32];
46 char buf3[32];
47 char buf4[32];
48 char buf5[32];
49 char buf6[32];
50 double seedRatio;
51 bool const hasSeedRatio = tr_torrentGetSeedRatio(tor, &seedRatio);
52
53 if (!isDone) /* downloading */
54 {
55 g_string_append_printf(gstr,
56 /* %1$s is how much we've got,
57 %2$s is how much we'll have when done,
58 %3$s%% is a percentage of the two */
59 _("%1$s of %2$s (%3$s%%)"),
60 tr_strlsize(buf1, haveTotal, sizeof(buf1)),
61 tr_strlsize(buf2, st->sizeWhenDone, sizeof(buf2)),
62 tr_strlpercent(buf3, st->percentDone * 100.0, sizeof(buf3)));
63 }
64 else if (!isSeed) /* partial seeds */
65 {
66 if (hasSeedRatio)
67 {
68 g_string_append_printf(gstr,
69 /* %1$s is how much we've got,
70 %2$s is the torrent's total size,
71 %3$s%% is a percentage of the two,
72 %4$s is how much we've uploaded,
73 %5$s is our upload-to-download ratio,
74 %6$s is the ratio we want to reach before we stop uploading */
75 _("%1$s of %2$s (%3$s%%), uploaded %4$s (Ratio: %5$s Goal: %6$s)"),
76 tr_strlsize(buf1, haveTotal, sizeof(buf1)),
77 tr_strlsize(buf2, info->totalSize, sizeof(buf2)),
78 tr_strlpercent(buf3, st->percentComplete * 100.0, sizeof(buf3)),
79 tr_strlsize(buf4, st->uploadedEver, sizeof(buf4)),
80 tr_strlratio(buf5, st->ratio, sizeof(buf5)),
81 tr_strlratio(buf6, seedRatio, sizeof(buf6)));
82 }
83 else
84 {
85 g_string_append_printf(gstr,
86 /* %1$s is how much we've got,
87 %2$s is the torrent's total size,
88 %3$s%% is a percentage of the two,
89 %4$s is how much we've uploaded,
90 %5$s is our upload-to-download ratio */
91 _("%1$s of %2$s (%3$s%%), uploaded %4$s (Ratio: %5$s)"),
92 tr_strlsize(buf1, haveTotal, sizeof(buf1)),
93 tr_strlsize(buf2, info->totalSize, sizeof(buf2)),
94 tr_strlpercent(buf3, st->percentComplete * 100.0, sizeof(buf3)),
95 tr_strlsize(buf4, st->uploadedEver, sizeof(buf4)),
96 tr_strlratio(buf5, st->ratio, sizeof(buf5)));
97 }
98 }
99 else /* seeding */
100 {
101 if (hasSeedRatio)
102 {
103 g_string_append_printf(gstr,
104 /* %1$s is the torrent's total size,
105 %2$s is how much we've uploaded,
106 %3$s is our upload-to-download ratio,
107 %4$s is the ratio we want to reach before we stop uploading */
108 _("%1$s, uploaded %2$s (Ratio: %3$s Goal: %4$s)"),
109 tr_strlsize(buf1, info->totalSize, sizeof(buf1)),
110 tr_strlsize(buf2, st->uploadedEver, sizeof(buf2)),
111 tr_strlratio(buf3, st->ratio, sizeof(buf3)),
112 tr_strlratio(buf4, seedRatio, sizeof(buf4)));
113 }
114 else /* seeding w/o a ratio */
115 {
116 g_string_append_printf(gstr,
117 /* %1$s is the torrent's total size,
118 %2$s is how much we've uploaded,
119 %3$s is our upload-to-download ratio */
120 _("%1$s, uploaded %2$s (Ratio: %3$s)"),
121 tr_strlsize(buf1, info->totalSize, sizeof(buf1)),
122 tr_strlsize(buf2, st->uploadedEver, sizeof(buf2)),
123 tr_strlratio(buf3, st->ratio, sizeof(buf3)));
124 }
125 }
126
127 /* add time when downloading */
128 if (st->activity == TR_STATUS_DOWNLOAD || (hasSeedRatio && st->activity == TR_STATUS_SEED))
129 {
130 int const eta = st->eta;
131 g_string_append(gstr, " - ");
132
133 if (eta < 0)
134 {
135 g_string_append(gstr, _("Remaining time unknown"));
136 }
137 else
138 {
139 char timestr[128];
140 tr_strltime(timestr, eta, sizeof(timestr));
141 /* time remaining */
142 g_string_append_printf(gstr, _("%s remaining"), timestr);
143 }
144 }
145 }
146
getShortTransferString(tr_torrent const * tor,tr_stat const * st,double uploadSpeed_KBps,double downloadSpeed_KBps,char * buf,size_t buflen)147 static char* getShortTransferString(tr_torrent const* tor, tr_stat const* st, double uploadSpeed_KBps,
148 double downloadSpeed_KBps, char* buf, size_t buflen)
149 {
150 bool const haveMeta = tr_torrentHasMetadata(tor);
151 bool const haveUp = haveMeta && st->peersGettingFromUs > 0;
152 bool const haveDown = haveMeta && (st->peersSendingToUs > 0 || st->webseedsSendingToUs > 0);
153
154 if (haveDown)
155 {
156 char dnStr[32];
157 char upStr[32];
158 tr_formatter_speed_KBps(dnStr, downloadSpeed_KBps, sizeof(dnStr));
159 tr_formatter_speed_KBps(upStr, uploadSpeed_KBps, sizeof(upStr));
160
161 /* down speed, down symbol, up speed, up symbol */
162 g_snprintf(buf, buflen, _("%1$s %2$s %3$s %4$s"), dnStr, gtr_get_unicode_string(GTR_UNICODE_DOWN), upStr,
163 gtr_get_unicode_string(GTR_UNICODE_UP));
164 }
165 else if (haveUp)
166 {
167 char upStr[32];
168 tr_formatter_speed_KBps(upStr, uploadSpeed_KBps, sizeof(upStr));
169
170 /* up speed, up symbol */
171 g_snprintf(buf, buflen, _("%1$s %2$s"), upStr, gtr_get_unicode_string(GTR_UNICODE_UP));
172 }
173 else if (st->isStalled)
174 {
175 g_strlcpy(buf, _("Stalled"), buflen);
176 }
177 else
178 {
179 *buf = '\0';
180 }
181
182 return buf;
183 }
184
getShortStatusString(GString * gstr,tr_torrent const * tor,tr_stat const * st,double uploadSpeed_KBps,double downloadSpeed_KBps)185 static void getShortStatusString(GString* gstr, tr_torrent const* tor, tr_stat const* st, double uploadSpeed_KBps,
186 double downloadSpeed_KBps)
187 {
188 switch (st->activity)
189 {
190 case TR_STATUS_STOPPED:
191 g_string_append(gstr, st->finished ? _("Finished") : _("Paused"));
192 break;
193
194 case TR_STATUS_CHECK_WAIT:
195 g_string_append(gstr, _("Queued for verification"));
196 break;
197
198 case TR_STATUS_DOWNLOAD_WAIT:
199 g_string_append(gstr, _("Queued for download"));
200 break;
201
202 case TR_STATUS_SEED_WAIT:
203 g_string_append(gstr, _("Queued for seeding"));
204 break;
205
206 case TR_STATUS_CHECK:
207 g_string_append_printf(gstr, _("Verifying local data (%.1f%% tested)"),
208 tr_truncd(st->recheckProgress * 100.0, 1));
209 break;
210
211 case TR_STATUS_DOWNLOAD:
212 case TR_STATUS_SEED:
213 {
214 char speedStr[64];
215 char ratioStr[64];
216 tr_strlratio(ratioStr, st->ratio, sizeof(ratioStr));
217 getShortTransferString(tor, st, uploadSpeed_KBps, downloadSpeed_KBps, speedStr, sizeof(speedStr));
218 /* download/upload speed, ratio */
219 g_string_append_printf(gstr, "%1$s Ratio: %2$s", speedStr, ratioStr);
220 break;
221 }
222
223 default:
224 break;
225 }
226 }
227
getStatusString(GString * gstr,tr_torrent const * tor,tr_stat const * st,double const uploadSpeed_KBps,double const downloadSpeed_KBps)228 static void getStatusString(GString* gstr, tr_torrent const* tor, tr_stat const* st, double const uploadSpeed_KBps,
229 double const downloadSpeed_KBps)
230 {
231 if (st->error != 0)
232 {
233 char const* fmt[] =
234 {
235 NULL,
236 N_("Tracker gave a warning: \"%s\""),
237 N_("Tracker gave an error: \"%s\""),
238 N_("Error: %s")
239 };
240
241 g_string_append_printf(gstr, _(fmt[st->error]), st->errorString);
242 }
243 else
244 {
245 switch (st->activity)
246 {
247 case TR_STATUS_STOPPED:
248 case TR_STATUS_CHECK_WAIT:
249 case TR_STATUS_CHECK:
250 case TR_STATUS_DOWNLOAD_WAIT:
251 case TR_STATUS_SEED_WAIT:
252 {
253 getShortStatusString(gstr, tor, st, uploadSpeed_KBps, downloadSpeed_KBps);
254 break;
255 }
256
257 case TR_STATUS_DOWNLOAD:
258 {
259 if (!tr_torrentHasMetadata(tor))
260 {
261 /* Downloading metadata from 2 peer (s)(50% done) */
262 g_string_append_printf(gstr, _("Downloading metadata from %1$'d %2$s (%3$d%% done)"), st->peersConnected,
263 ngettext("peer", "peers", st->peersConnected), (int)(100.0 * st->metadataPercentComplete));
264 }
265 else if (st->peersSendingToUs != 0 && st->webseedsSendingToUs != 0)
266 {
267 /* Downloading from 2 of 3 peer (s) and 2 webseed (s) */
268 g_string_append_printf(gstr, _("Downloading from %1$'d of %2$'d %3$s and %4$'d %5$s"), st->peersSendingToUs,
269 st->peersConnected, ngettext("peer", "peers", st->peersConnected), st->webseedsSendingToUs,
270 ngettext("web seed", "web seeds", st->webseedsSendingToUs));
271 }
272 else if (st->webseedsSendingToUs != 0)
273 {
274 /* Downloading from 3 web seed (s) */
275 g_string_append_printf(gstr, _("Downloading from %1$'d %2$s"), st->webseedsSendingToUs,
276 ngettext("web seed", "web seeds", st->webseedsSendingToUs));
277 }
278 else
279 {
280 /* Downloading from 2 of 3 peer (s) */
281 g_string_append_printf(gstr, _("Downloading from %1$'d of %2$'d %3$s"), st->peersSendingToUs,
282 st->peersConnected, ngettext("peer", "peers", st->peersConnected));
283 }
284
285 break;
286 }
287
288 case TR_STATUS_SEED:
289 g_string_append_printf(gstr, ngettext("Seeding to %1$'d of %2$'d connected peer",
290 "Seeding to %1$'d of %2$'d connected peers", st->peersConnected), st->peersGettingFromUs, st->peersConnected);
291 break;
292 }
293 }
294
295 if (st->activity != TR_STATUS_CHECK_WAIT && st->activity != TR_STATUS_CHECK && st->activity != TR_STATUS_DOWNLOAD_WAIT &&
296 st->activity != TR_STATUS_SEED_WAIT && st->activity != TR_STATUS_STOPPED)
297 {
298 char buf[256];
299 getShortTransferString(tor, st, uploadSpeed_KBps, downloadSpeed_KBps, buf, sizeof(buf));
300
301 if (!tr_str_is_empty(buf))
302 {
303 g_string_append_printf(gstr, " - %s", buf);
304 }
305 }
306 }
307
308 /***
309 ****
310 ***/
311
312 typedef struct TorrentCellRendererPrivate
313 {
314 tr_torrent* tor;
315 GtkCellRenderer* text_renderer;
316 GtkCellRenderer* progress_renderer;
317 GtkCellRenderer* icon_renderer;
318 GString* gstr1;
319 GString* gstr2;
320 int bar_height;
321
322 /* Use this instead of tr_stat.pieceUploadSpeed so that the model can
323 control when the speed displays get updated. This is done to keep
324 the individual torrents' speeds and the status bar's overall speed
325 in sync even if they refresh at slightly different times */
326 double upload_speed_KBps;
327
328 /* @see upload_speed_Bps */
329 double download_speed_KBps;
330
331 gboolean compact;
332 }
333 TorrentCellRendererPrivate;
334
335 /***
336 ****
337 ***/
338
get_icon(tr_torrent const * tor,GtkIconSize icon_size,GtkWidget * for_widget)339 static GdkPixbuf* get_icon(tr_torrent const* tor, GtkIconSize icon_size, GtkWidget* for_widget)
340 {
341 char const* mime_type;
342 tr_info const* info = tr_torrentInfo(tor);
343
344 if (info->fileCount == 0)
345 {
346 mime_type = UNKNOWN_MIME_TYPE;
347 }
348 else if (info->fileCount > 1)
349 {
350 mime_type = DIRECTORY_MIME_TYPE;
351 }
352 else if (strchr(info->files[0].name, '/') != NULL)
353 {
354 mime_type = DIRECTORY_MIME_TYPE;
355 }
356 else
357 {
358 mime_type = gtr_get_mime_type_from_filename(info->files[0].name);
359 }
360
361 return gtr_get_mime_type_icon(mime_type, icon_size, for_widget);
362 }
363
364 /***
365 ****
366 ***/
367
gtr_cell_renderer_get_preferred_size(GtkCellRenderer * renderer,GtkWidget * widget,GtkRequisition * minimum_size,GtkRequisition * natural_size)368 static void gtr_cell_renderer_get_preferred_size(GtkCellRenderer* renderer, GtkWidget* widget, GtkRequisition* minimum_size,
369 GtkRequisition* natural_size)
370 {
371 gtk_cell_renderer_get_preferred_size(renderer, widget, minimum_size, natural_size);
372 }
373
get_size_compact(TorrentCellRenderer * cell,GtkWidget * widget,gint * width,gint * height)374 static void get_size_compact(TorrentCellRenderer* cell, GtkWidget* widget, gint* width, gint* height)
375 {
376 int xpad;
377 int ypad;
378 GtkRequisition icon_size;
379 GtkRequisition name_size;
380 GtkRequisition stat_size;
381 char const* name;
382 GdkPixbuf* icon;
383
384 struct TorrentCellRendererPrivate* p = cell->priv;
385 tr_torrent const* tor = p->tor;
386 tr_stat const* st = tr_torrentStatCached((tr_torrent*)tor);
387 GString* gstr_stat = p->gstr1;
388
389 icon = get_icon(tor, COMPACT_ICON_SIZE, widget);
390 name = tr_torrentName(tor);
391 g_string_truncate(gstr_stat, 0);
392 getShortStatusString(gstr_stat, tor, st, p->upload_speed_KBps, p->download_speed_KBps);
393 gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
394
395 /* get the idealized cell dimensions */
396 g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
397 gtr_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL, &icon_size);
398 g_object_set(p->text_renderer, "text", name, "ellipsize", PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL);
399 gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &name_size);
400 g_object_set(p->text_renderer, "text", gstr_stat->str, "scale", SMALL_SCALE, NULL);
401 gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &stat_size);
402
403 /**
404 *** LAYOUT
405 **/
406
407 #define BAR_WIDTH 50
408
409 if (width != NULL)
410 {
411 *width = xpad * 2 + icon_size.width + GUI_PAD + BAR_WIDTH + GUI_PAD + stat_size.width;
412 }
413
414 if (height != NULL)
415 {
416 *height = ypad * 2 + MAX(name_size.height, p->bar_height);
417 }
418
419 /* cleanup */
420 g_object_unref(icon);
421 }
422
get_size_full(TorrentCellRenderer * cell,GtkWidget * widget,gint * width,gint * height)423 static void get_size_full(TorrentCellRenderer* cell, GtkWidget* widget, gint* width, gint* height)
424 {
425 int xpad;
426 int ypad;
427 GtkRequisition icon_size;
428 GtkRequisition name_size;
429 GtkRequisition stat_size;
430 GtkRequisition prog_size;
431 char const* name;
432 GdkPixbuf* icon;
433
434 struct TorrentCellRendererPrivate* p = cell->priv;
435 tr_torrent const* tor = p->tor;
436 tr_stat const* st = tr_torrentStatCached((tr_torrent*)tor);
437 tr_info const* inf = tr_torrentInfo(tor);
438 GString* gstr_prog = p->gstr1;
439 GString* gstr_stat = p->gstr2;
440
441 icon = get_icon(tor, FULL_ICON_SIZE, widget);
442 name = tr_torrentName(tor);
443 g_string_truncate(gstr_stat, 0);
444 getStatusString(gstr_stat, tor, st, p->upload_speed_KBps, p->download_speed_KBps);
445 g_string_truncate(gstr_prog, 0);
446 getProgressString(gstr_prog, tor, inf, st);
447 gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
448
449 /* get the idealized cell dimensions */
450 g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
451 gtr_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL, &icon_size);
452 g_object_set(p->text_renderer, "text", name, "weight", PANGO_WEIGHT_BOLD, "scale", 1.0, "ellipsize", PANGO_ELLIPSIZE_NONE,
453 NULL);
454 gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &name_size);
455 g_object_set(p->text_renderer, "text", gstr_prog->str, "weight", PANGO_WEIGHT_NORMAL, "scale", SMALL_SCALE, NULL);
456 gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &prog_size);
457 g_object_set(p->text_renderer, "text", gstr_stat->str, NULL);
458 gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &stat_size);
459
460 /**
461 *** LAYOUT
462 **/
463
464 if (width != NULL)
465 {
466 *width = xpad * 2 + icon_size.width + GUI_PAD + MAX(prog_size.width, stat_size.width);
467 }
468
469 if (height != NULL)
470 {
471 *height = ypad * 2 + name_size.height + prog_size.height + GUI_PAD_SMALL + p->bar_height + GUI_PAD_SMALL +
472 stat_size.height;
473 }
474
475 /* cleanup */
476 g_object_unref(icon);
477 }
478
torrent_cell_renderer_get_size(GtkCellRenderer * cell,GtkWidget * widget,GdkRectangle const * cell_area,gint * x_offset,gint * y_offset,gint * width,gint * height)479 static void torrent_cell_renderer_get_size(GtkCellRenderer* cell, GtkWidget* widget, GdkRectangle const* cell_area,
480 gint* x_offset, gint* y_offset, gint* width, gint* height)
481 {
482 TorrentCellRenderer* self = TORRENT_CELL_RENDERER(cell);
483
484 if (self != NULL && self->priv->tor != NULL)
485 {
486 int w;
487 int h;
488 struct TorrentCellRendererPrivate* p = self->priv;
489
490 if (p->compact)
491 {
492 get_size_compact(TORRENT_CELL_RENDERER(cell), widget, &w, &h);
493 }
494 else
495 {
496 get_size_full(TORRENT_CELL_RENDERER(cell), widget, &w, &h);
497 }
498
499 if (width != NULL)
500 {
501 *width = w;
502 }
503
504 if (height != NULL)
505 {
506 *height = h;
507 }
508
509 if (x_offset != NULL)
510 {
511 *x_offset = cell_area ? cell_area->x : 0;
512 }
513
514 if (y_offset != NULL)
515 {
516 int xpad;
517 int ypad;
518 gtk_cell_renderer_get_padding(cell, &xpad, &ypad);
519 *y_offset = cell_area ? (int)((cell_area->height - (ypad * 2 + h)) / 2.0) : 0;
520 }
521 }
522 }
523
524 typedef GdkRGBA GtrColor;
525 #define FOREGROUND_COLOR_KEY "foreground-rgba"
526
get_text_color(GtkWidget * w,tr_stat const * st,GtrColor * setme)527 static void get_text_color(GtkWidget* w, tr_stat const* st, GtrColor* setme)
528 {
529 static GdkRGBA const red = { 1.0, 0, 0, 0 };
530
531 if (st->error != 0)
532 {
533 *setme = red;
534 }
535 else if (st->activity == TR_STATUS_STOPPED)
536 {
537 gtk_style_context_get_color(gtk_widget_get_style_context(w), GTK_STATE_FLAG_INSENSITIVE, setme);
538 }
539 else
540 {
541 gtk_style_context_get_color(gtk_widget_get_style_context(w), GTK_STATE_FLAG_NORMAL, setme);
542 }
543 }
544
get_percent_done(tr_torrent const * tor,tr_stat const * st,bool * seed)545 static double get_percent_done(tr_torrent const* tor, tr_stat const* st, bool* seed)
546 {
547 double d;
548
549 if (st->activity == TR_STATUS_SEED && tr_torrentGetSeedRatio(tor, &d))
550 {
551 *seed = true;
552 d = MAX(0.0, st->seedRatioPercentDone);
553 }
554 else
555 {
556 *seed = false;
557 d = MAX(0.0, st->percentDone);
558 }
559
560 return d;
561 }
562
563 typedef cairo_t GtrDrawable;
564
gtr_cell_renderer_render(GtkCellRenderer * renderer,GtrDrawable * drawable,GtkWidget * widget,GdkRectangle const * area,GtkCellRendererState flags)565 static void gtr_cell_renderer_render(GtkCellRenderer* renderer, GtrDrawable* drawable, GtkWidget* widget,
566 GdkRectangle const* area, GtkCellRendererState flags)
567 {
568 gtk_cell_renderer_render(renderer, drawable, widget, area, area, flags);
569 }
570
render_compact(TorrentCellRenderer * cell,GtrDrawable * window,GtkWidget * widget,GdkRectangle const * background_area,GdkRectangle const * cell_area UNUSED,GtkCellRendererState flags)571 static void render_compact(TorrentCellRenderer* cell, GtrDrawable* window, GtkWidget* widget,
572 GdkRectangle const* background_area, GdkRectangle const* cell_area UNUSED,
573 GtkCellRendererState flags)
574 {
575 int xpad;
576 int ypad;
577 GtkRequisition size;
578 GdkRectangle icon_area;
579 GdkRectangle name_area;
580 GdkRectangle stat_area;
581 GdkRectangle prog_area;
582 GdkRectangle fill_area;
583 char const* name;
584 GdkPixbuf* icon;
585 GtrColor text_color;
586 bool seed;
587
588 struct TorrentCellRendererPrivate* p = cell->priv;
589 tr_torrent const* tor = p->tor;
590 tr_stat const* st = tr_torrentStatCached((tr_torrent*)tor);
591 gboolean const active = st->activity != TR_STATUS_STOPPED && st->activity != TR_STATUS_DOWNLOAD_WAIT &&
592 st->activity != TR_STATUS_SEED_WAIT;
593 double const percentDone = get_percent_done(tor, st, &seed);
594 gboolean const sensitive = active || st->error;
595 GString* gstr_stat = p->gstr1;
596
597 icon = get_icon(tor, COMPACT_ICON_SIZE, widget);
598 name = tr_torrentName(tor);
599 g_string_truncate(gstr_stat, 0);
600 getShortStatusString(gstr_stat, tor, st, p->upload_speed_KBps, p->download_speed_KBps);
601 gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
602 get_text_color(widget, st, &text_color);
603
604 fill_area = *background_area;
605 fill_area.x += xpad;
606 fill_area.y += ypad;
607 fill_area.width -= xpad * 2;
608 fill_area.height -= ypad * 2;
609 icon_area = name_area = stat_area = prog_area = fill_area;
610
611 g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
612 gtr_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL, &size);
613 icon_area.width = size.width;
614 g_object_set(p->text_renderer, "text", name, "ellipsize", PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL);
615 gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &size);
616 name_area.width = size.width;
617 g_object_set(p->text_renderer, "text", gstr_stat->str, "scale", SMALL_SCALE, NULL);
618 gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &size);
619 stat_area.width = size.width;
620
621 icon_area.x = fill_area.x;
622 prog_area.x = fill_area.x + fill_area.width - BAR_WIDTH;
623 prog_area.width = BAR_WIDTH;
624 stat_area.x = prog_area.x - GUI_PAD - stat_area.width;
625 name_area.x = icon_area.x + icon_area.width + GUI_PAD;
626 name_area.y = fill_area.y;
627 name_area.width = stat_area.x - GUI_PAD - name_area.x;
628
629 /**
630 *** RENDER
631 **/
632
633 g_object_set(p->icon_renderer, "pixbuf", icon, "sensitive", sensitive, NULL);
634 gtr_cell_renderer_render(p->icon_renderer, window, widget, &icon_area, flags);
635 g_object_set(p->progress_renderer, "value", (int)(percentDone * 100.0), "text", NULL, "sensitive", sensitive, NULL);
636 gtr_cell_renderer_render(p->progress_renderer, window, widget, &prog_area, flags);
637 g_object_set(p->text_renderer, "text", gstr_stat->str, "scale", SMALL_SCALE, "ellipsize", PANGO_ELLIPSIZE_END,
638 FOREGROUND_COLOR_KEY, &text_color, NULL);
639 gtr_cell_renderer_render(p->text_renderer, window, widget, &stat_area, flags);
640 g_object_set(p->text_renderer, "text", name, "scale", 1.0, FOREGROUND_COLOR_KEY, &text_color, NULL);
641 gtr_cell_renderer_render(p->text_renderer, window, widget, &name_area, flags);
642
643 /* cleanup */
644 g_object_unref(icon);
645 }
646
render_full(TorrentCellRenderer * cell,GtrDrawable * window,GtkWidget * widget,GdkRectangle const * background_area,GdkRectangle const * cell_area UNUSED,GtkCellRendererState flags)647 static void render_full(TorrentCellRenderer* cell, GtrDrawable* window, GtkWidget* widget, GdkRectangle const* background_area,
648 GdkRectangle const* cell_area UNUSED, GtkCellRendererState flags)
649 {
650 int xpad;
651 int ypad;
652 GtkRequisition size;
653 GdkRectangle fill_area;
654 GdkRectangle icon_area;
655 GdkRectangle name_area;
656 GdkRectangle stat_area;
657 GdkRectangle prog_area;
658 GdkRectangle prct_area;
659 char const* name;
660 GdkPixbuf* icon;
661 GtrColor text_color;
662 bool seed;
663
664 struct TorrentCellRendererPrivate* p = cell->priv;
665 tr_torrent const* tor = p->tor;
666 tr_stat const* st = tr_torrentStatCached((tr_torrent*)tor);
667 tr_info const* inf = tr_torrentInfo(tor);
668 gboolean const active = st->activity != TR_STATUS_STOPPED && st->activity != TR_STATUS_DOWNLOAD_WAIT &&
669 st->activity != TR_STATUS_SEED_WAIT;
670 double const percentDone = get_percent_done(tor, st, &seed);
671 gboolean const sensitive = active || st->error;
672 GString* gstr_prog = p->gstr1;
673 GString* gstr_stat = p->gstr2;
674
675 icon = get_icon(tor, FULL_ICON_SIZE, widget);
676 name = tr_torrentName(tor);
677 g_string_truncate(gstr_prog, 0);
678 getProgressString(gstr_prog, tor, inf, st);
679 g_string_truncate(gstr_stat, 0);
680 getStatusString(gstr_stat, tor, st, p->upload_speed_KBps, p->download_speed_KBps);
681 gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
682 get_text_color(widget, st, &text_color);
683
684 /* get the idealized cell dimensions */
685 g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
686 gtr_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL, &size);
687 icon_area.width = size.width;
688 icon_area.height = size.height;
689 g_object_set(p->text_renderer, "text", name, "weight", PANGO_WEIGHT_BOLD, "ellipsize", PANGO_ELLIPSIZE_NONE, "scale", 1.0,
690 NULL);
691 gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &size);
692 name_area.width = size.width;
693 name_area.height = size.height;
694 g_object_set(p->text_renderer, "text", gstr_prog->str, "weight", PANGO_WEIGHT_NORMAL, "scale", SMALL_SCALE, NULL);
695 gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &size);
696 prog_area.width = size.width;
697 prog_area.height = size.height;
698 g_object_set(p->text_renderer, "text", gstr_stat->str, NULL);
699 gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &size);
700 stat_area.width = size.width;
701 stat_area.height = size.height;
702
703 /**
704 *** LAYOUT
705 **/
706
707 fill_area = *background_area;
708 fill_area.x += xpad;
709 fill_area.y += ypad;
710 fill_area.width -= xpad * 2;
711 fill_area.height -= ypad * 2;
712
713 /* icon */
714 icon_area.x = fill_area.x;
715 icon_area.y = fill_area.y + (fill_area.height - icon_area.height) / 2;
716
717 /* name */
718 name_area.x = icon_area.x + icon_area.width + GUI_PAD;
719 name_area.y = fill_area.y;
720 name_area.width = fill_area.width - GUI_PAD - icon_area.width - GUI_PAD_SMALL;
721
722 /* prog */
723 prog_area.x = name_area.x;
724 prog_area.y = name_area.y + name_area.height;
725 prog_area.width = name_area.width;
726
727 /* progressbar */
728 prct_area.x = prog_area.x;
729 prct_area.y = prog_area.y + prog_area.height + GUI_PAD_SMALL;
730 prct_area.width = prog_area.width;
731 prct_area.height = p->bar_height;
732
733 /* status */
734 stat_area.x = prct_area.x;
735 stat_area.y = prct_area.y + prct_area.height + GUI_PAD_SMALL;
736 stat_area.width = prct_area.width;
737
738 /**
739 *** RENDER
740 **/
741
742 g_object_set(p->icon_renderer, "pixbuf", icon, "sensitive", sensitive, NULL);
743 gtr_cell_renderer_render(p->icon_renderer, window, widget, &icon_area, flags);
744 g_object_set(p->text_renderer, "text", name, "scale", 1.0, FOREGROUND_COLOR_KEY, &text_color, "ellipsize",
745 PANGO_ELLIPSIZE_END, "weight", PANGO_WEIGHT_BOLD, NULL);
746 gtr_cell_renderer_render(p->text_renderer, window, widget, &name_area, flags);
747 g_object_set(p->text_renderer, "text", gstr_prog->str, "scale", SMALL_SCALE, "weight", PANGO_WEIGHT_NORMAL, NULL);
748 gtr_cell_renderer_render(p->text_renderer, window, widget, &prog_area, flags);
749 g_object_set(p->progress_renderer, "value", (int)(percentDone * 100.0), "text", "", "sensitive", sensitive, NULL);
750 gtr_cell_renderer_render(p->progress_renderer, window, widget, &prct_area, flags);
751 g_object_set(p->text_renderer, "text", gstr_stat->str, FOREGROUND_COLOR_KEY, &text_color, NULL);
752 gtr_cell_renderer_render(p->text_renderer, window, widget, &stat_area, flags);
753
754 /* cleanup */
755 g_object_unref(icon);
756 }
757
torrent_cell_renderer_render(GtkCellRenderer * cell,GtrDrawable * window,GtkWidget * widget,GdkRectangle const * background_area,GdkRectangle const * cell_area,GtkCellRendererState flags)758 static void torrent_cell_renderer_render(GtkCellRenderer* cell, GtrDrawable* window, GtkWidget* widget,
759 GdkRectangle const* background_area, GdkRectangle const* cell_area,
760 GtkCellRendererState flags)
761 {
762 TorrentCellRenderer* self = TORRENT_CELL_RENDERER(cell);
763
764 #ifdef TEST_RTL
765 GtkTextDirection real_dir = gtk_widget_get_direction(widget);
766 gtk_widget_set_direction(widget, GTK_TEXT_DIR_RTL);
767 #endif
768
769 if (self != NULL && self->priv->tor != NULL)
770 {
771 struct TorrentCellRendererPrivate* p = self->priv;
772
773 if (p->compact)
774 {
775 render_compact(self, window, widget, background_area, cell_area, flags);
776 }
777 else
778 {
779 render_full(self, window, widget, background_area, cell_area, flags);
780 }
781 }
782
783 #ifdef TEST_RTL
784 gtk_widget_set_direction(widget, real_dir);
785 #endif
786 }
787
torrent_cell_renderer_set_property(GObject * object,guint property_id,GValue const * v,GParamSpec * pspec)788 static void torrent_cell_renderer_set_property(GObject* object, guint property_id, GValue const* v, GParamSpec* pspec)
789 {
790 TorrentCellRenderer* self = TORRENT_CELL_RENDERER(object);
791 struct TorrentCellRendererPrivate* p = self->priv;
792
793 switch (property_id)
794 {
795 case P_TORRENT:
796 p->tor = g_value_get_pointer(v);
797 break;
798
799 case P_UPLOAD_SPEED:
800 p->upload_speed_KBps = g_value_get_double(v);
801 break;
802
803 case P_DOWNLOAD_SPEED:
804 p->download_speed_KBps = g_value_get_double(v);
805 break;
806
807 case P_BAR_HEIGHT:
808 p->bar_height = g_value_get_int(v);
809 break;
810
811 case P_COMPACT:
812 p->compact = g_value_get_boolean(v);
813 break;
814
815 default:
816 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
817 break;
818 }
819 }
820
torrent_cell_renderer_get_property(GObject * object,guint property_id,GValue * v,GParamSpec * pspec)821 static void torrent_cell_renderer_get_property(GObject* object, guint property_id, GValue* v, GParamSpec* pspec)
822 {
823 TorrentCellRenderer const* self = TORRENT_CELL_RENDERER(object);
824 struct TorrentCellRendererPrivate* p = self->priv;
825
826 switch (property_id)
827 {
828 case P_TORRENT:
829 g_value_set_pointer(v, p->tor);
830 break;
831
832 case P_UPLOAD_SPEED:
833 g_value_set_double(v, p->upload_speed_KBps);
834 break;
835
836 case P_DOWNLOAD_SPEED:
837 g_value_set_double(v, p->download_speed_KBps);
838 break;
839
840 case P_BAR_HEIGHT:
841 g_value_set_int(v, p->bar_height);
842 break;
843
844 case P_COMPACT:
845 g_value_set_boolean(v, p->compact);
846 break;
847
848 default:
849 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
850 break;
851 }
852 }
853
G_DEFINE_TYPE_WITH_CODE(TorrentCellRenderer,torrent_cell_renderer,GTK_TYPE_CELL_RENDERER,G_ADD_PRIVATE (TorrentCellRenderer))854 G_DEFINE_TYPE_WITH_CODE(TorrentCellRenderer, torrent_cell_renderer, GTK_TYPE_CELL_RENDERER, G_ADD_PRIVATE(TorrentCellRenderer))
855
856 static void torrent_cell_renderer_dispose(GObject* o)
857 {
858 TorrentCellRenderer* r = TORRENT_CELL_RENDERER(o);
859
860 if (r != NULL && r->priv != NULL)
861 {
862 g_string_free(r->priv->gstr1, TRUE);
863 g_string_free(r->priv->gstr2, TRUE);
864 g_object_unref(G_OBJECT(r->priv->text_renderer));
865 g_object_unref(G_OBJECT(r->priv->progress_renderer));
866 g_object_unref(G_OBJECT(r->priv->icon_renderer));
867 r->priv = NULL;
868 }
869
870 G_OBJECT_CLASS(torrent_cell_renderer_parent_class)->dispose(o);
871 }
872
torrent_cell_renderer_class_init(TorrentCellRendererClass * klass)873 static void torrent_cell_renderer_class_init(TorrentCellRendererClass* klass)
874 {
875 GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
876 GtkCellRendererClass* cell_class = GTK_CELL_RENDERER_CLASS(klass);
877
878 cell_class->render = torrent_cell_renderer_render;
879 cell_class->get_size = torrent_cell_renderer_get_size;
880 gobject_class->set_property = torrent_cell_renderer_set_property;
881 gobject_class->get_property = torrent_cell_renderer_get_property;
882 gobject_class->dispose = torrent_cell_renderer_dispose;
883
884 g_object_class_install_property(gobject_class, P_TORRENT,
885 g_param_spec_pointer("torrent", NULL, "tr_torrent*", G_PARAM_READWRITE));
886
887 g_object_class_install_property(gobject_class, P_UPLOAD_SPEED,
888 g_param_spec_double("piece-upload-speed", NULL, "tr_stat.pieceUploadSpeed_KBps", 0, INT_MAX, 0, G_PARAM_READWRITE));
889
890 g_object_class_install_property(gobject_class, P_DOWNLOAD_SPEED,
891 g_param_spec_double("piece-download-speed", NULL, "tr_stat.pieceDownloadSpeed_KBps", 0, INT_MAX, 0, G_PARAM_READWRITE));
892
893 g_object_class_install_property(gobject_class, P_BAR_HEIGHT,
894 g_param_spec_int("bar-height", NULL, "Bar Height", 1, INT_MAX, DEFAULT_BAR_HEIGHT, G_PARAM_READWRITE));
895
896 g_object_class_install_property(gobject_class, P_COMPACT,
897 g_param_spec_boolean("compact", NULL, "Compact Mode", FALSE, G_PARAM_READWRITE));
898 }
899
torrent_cell_renderer_init(TorrentCellRenderer * self)900 static void torrent_cell_renderer_init(TorrentCellRenderer* self)
901 {
902 struct TorrentCellRendererPrivate* p;
903
904 #if GLIB_CHECK_VERSION(2, 58, 0)
905 p = self->priv = torrent_cell_renderer_get_instance_private(self);
906 #else
907 p = self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, TORRENT_CELL_RENDERER_TYPE, struct TorrentCellRendererPrivate);
908 #endif
909
910 p->tor = NULL;
911 p->gstr1 = g_string_new(NULL);
912 p->gstr2 = g_string_new(NULL);
913 p->text_renderer = gtk_cell_renderer_text_new();
914 g_object_set(p->text_renderer, "xpad", 0, "ypad", 0, NULL);
915 p->progress_renderer = gtk_cell_renderer_progress_new();
916 p->icon_renderer = gtk_cell_renderer_pixbuf_new();
917 g_object_ref_sink(p->text_renderer);
918 g_object_ref_sink(p->progress_renderer);
919 g_object_ref_sink(p->icon_renderer);
920
921 p->bar_height = DEFAULT_BAR_HEIGHT;
922 }
923
torrent_cell_renderer_new(void)924 GtkCellRenderer* torrent_cell_renderer_new(void)
925 {
926 return (GtkCellRenderer*)g_object_new(TORRENT_CELL_RENDERER_TYPE, NULL);
927 }
928